diff --git a/packages/tron-wallet-snap/CHANGELOG.md b/packages/tron-wallet-snap/CHANGELOG.md index 29b0b54d0..e0ec215c7 100644 --- a/packages/tron-wallet-snap/CHANGELOG.md +++ b/packages/tron-wallet-snap/CHANGELOG.md @@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING** Bump `@metamask/keyring-snap-sdk` from `^9.2.1` to `^10.0.0` ([#214](https://github.com/MetaMask/internal-snaps/pull/214)) - **BREAKING** Bump `@metamask/snaps-sdk` from `^11.2.0` to `^12.0.1` ([#214](https://github.com/MetaMask/internal-snaps/pull/214)) +### Removed + +- **BREAKING** Remove the deprecated `asset` cluster of handlers: `onAssetHistoricalPrice`, `onAssetsConversion`, `onAssetsLookup` and `onAssetsMarketData` ([#263](https://github.com/MetaMask/internal-snaps/pull/263)) + ## [3.2.0] ### Added diff --git a/packages/tron-wallet-snap/snap.manifest.json b/packages/tron-wallet-snap/snap.manifest.json index ed8d3af3b..3cb331dc3 100644 --- a/packages/tron-wallet-snap/snap.manifest.json +++ b/packages/tron-wallet-snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/internal-snaps.git" }, "source": { - "shasum": "/8DB4dcdqQHoD+raJRPRe0ztQnTaOI8Lp8E2Yg6WCEM=", + "shasum": "P5tbNf6ZPq2tMKAaTnr8oVM+C+rBRVYrf8ACLshI01c=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -60,9 +60,6 @@ } ] }, - "endowment:assets": { - "scopes": ["tron:728126428"] - }, "endowment:messenger": { "actions": [ "RemoteFeatureFlagController:getState", diff --git a/packages/tron-wallet-snap/src/caching/useCache.test.ts b/packages/tron-wallet-snap/src/caching/useCache.test.ts deleted file mode 100644 index bfb302204..000000000 --- a/packages/tron-wallet-snap/src/caching/useCache.test.ts +++ /dev/null @@ -1,305 +0,0 @@ -import type { Serializable } from '@metamask/snap-networks-utils'; - -import type { ICache } from './ICache'; -import { CacheOptions, useCache } from './useCache'; - -// Define common cache options -const cacheOptions: CacheOptions = { - ttlMilliseconds: 1000, - functionName: 'testFunction', -}; - -type WithUseCacheCallback = (payload: { - actualExecutionSpy: jest.Mock, Serializable[]>; - cache: MockCache; - testFunction: () => Promise; - cachedTestFunction: () => Promise; - cachedTestFunctionWithArgs: (arg1: string, arg2: number) => Promise; - cachedTestFunctionWithComplexArgs: (obj: { - name: string; - age: number; - }) => Promise; -}) => void | Promise; - -type MockCache = ICache & { - get: jest.Mock, [string]>; - set: jest.Mock, [string, Serializable, (number | undefined)?]>; -}; - -/** - * Wraps tests for `useCache` by creating fresh cached functions backed by a - * mock cache. - * - * @param testFn - The test body receiving the cached functions. - * @returns A promise that resolves when the test function completes. - */ -async function withUseCache(testFn: WithUseCacheCallback): Promise { - // Reset mocks for each test - const actualExecutionSpy = jest - .fn, Serializable[]>() - .mockResolvedValue('test'); - - // Create a mock cache - const cache = { - get: jest.fn().mockResolvedValue(undefined), - set: jest.fn().mockResolvedValue(undefined), - } as unknown as MockCache; - - // Define original functions - const testFunction = async (): Promise => actualExecutionSpy(); - const testFunctionWithArgs = async ( - arg1: string, - arg2: number, - ): Promise => actualExecutionSpy(arg1, arg2); - const testFunctionWithComplexArgs = async (obj: { - name: string; - age: number; - }): Promise => actualExecutionSpy(obj); - - // Create cached versions - const cachedTestFunction = useCache(testFunction, cache, { - ...cacheOptions, - functionName: 'testFunction', - }); - - const cachedTestFunctionWithArgs = useCache(testFunctionWithArgs, cache, { - ...cacheOptions, - functionName: 'testFunctionWithArgs', - }); - - const cachedTestFunctionWithComplexArgs = useCache( - testFunctionWithComplexArgs, - cache, - { - ...cacheOptions, - functionName: 'testFunctionWithComplexArgs', - }, - ); - - await testFn({ - actualExecutionSpy, - cache, - testFunction, - cachedTestFunction, - cachedTestFunctionWithArgs, - cachedTestFunctionWithComplexArgs, - }); -} - -describe('useCache', () => { - describe('when the data is not cached', () => { - it('should cache the result of a function', async () => { - await withUseCache( - async ({ actualExecutionSpy, cache, cachedTestFunction }) => { - // No cached data - cache.get.mockResolvedValue(undefined); - - const result = await cachedTestFunction(); - - expect(result).toBe('test'); - expect(cache.get).toHaveBeenCalledTimes(1); - expect(actualExecutionSpy).toHaveBeenCalledTimes(1); - expect(cache.set).toHaveBeenCalledWith('testFunction:', 'test', 1000); - }, - ); - }); - }); - - describe('when the data is cached', () => { - it('should return the cached result', async () => { - await withUseCache( - async ({ actualExecutionSpy, cache, cachedTestFunction }) => { - // Init the cache with some data - cache.get.mockResolvedValue('test'); - // jest.spyOn(cache, 'get').mockResolvedValue('test'); - - const result = await cachedTestFunction(); - - expect(result).toBe('test'); - expect(cache.get).toHaveBeenCalledTimes(1); - expect(actualExecutionSpy).not.toHaveBeenCalled(); - expect(cache.set).not.toHaveBeenCalled(); - }, - ); - }); - }); - - describe('error handling', () => { - it('should propagate errors from the original function', async () => { - await withUseCache( - async ({ actualExecutionSpy, cachedTestFunction, cache }) => { - const error = new Error('Test error'); - actualExecutionSpy.mockRejectedValueOnce(error); - - await expect(cachedTestFunction()).rejects.toThrow('Test error'); - expect(cache.set).not.toHaveBeenCalled(); - }, - ); - }); - - it('should handle cache get errors gracefully', async () => { - await withUseCache( - async ({ actualExecutionSpy, cachedTestFunction, cache }) => { - cache.get.mockRejectedValueOnce(new Error('Cache error')); - actualExecutionSpy.mockResolvedValueOnce('test'); - - const result = await cachedTestFunction(); - - expect(result).toBe('test'); - expect(actualExecutionSpy).toHaveBeenCalledTimes(1); - expect(cache.set).toHaveBeenCalledWith('testFunction:', 'test', 1000); - }, - ); - }); - - it('should handle cache set errors gracefully', async () => { - await withUseCache( - async ({ actualExecutionSpy, cachedTestFunction, cache }) => { - cache.get.mockResolvedValue(undefined); - cache.set.mockRejectedValueOnce(new Error('Cache set error')); - actualExecutionSpy.mockResolvedValueOnce('test'); - - const result = await cachedTestFunction(); - - expect(result).toBe('test'); - expect(actualExecutionSpy).toHaveBeenCalledTimes(1); - }, - ); - }); - }); - - describe('different argument types', () => { - it('should handle primitive arguments correctly', async () => { - await withUseCache( - async ({ actualExecutionSpy, cache, cachedTestFunctionWithArgs }) => { - cache.get.mockResolvedValue(undefined); - cache.set.mockResolvedValueOnce(undefined); - actualExecutionSpy.mockResolvedValueOnce('test with args'); - - const result = await cachedTestFunctionWithArgs('hello', 42); - - expect(result).toBe('test with args'); - expect(cache.get).toHaveBeenCalledWith( - 'testFunctionWithArgs:"hello":42', - ); - expect(actualExecutionSpy).toHaveBeenCalledWith('hello', 42); - }, - ); - }); - - it('should handle complex object arguments correctly', async () => { - await withUseCache( - async ({ - actualExecutionSpy, - cache, - cachedTestFunctionWithComplexArgs, - }) => { - cache.get.mockResolvedValue(undefined); - cache.set.mockResolvedValueOnce(undefined); - actualExecutionSpy.mockResolvedValueOnce('test with complex args'); - - const testObj = { name: 'John', age: 30 }; - const result = await cachedTestFunctionWithComplexArgs(testObj); - - expect(result).toBe('test with complex args'); - expect(cache.get).toHaveBeenCalledWith( - 'testFunctionWithComplexArgs:{"name":"John","age":30}', - ); - expect(actualExecutionSpy).toHaveBeenCalledWith(testObj); - }, - ); - }); - }); - - describe('custom generateCacheKey', () => { - it('should use a custom key generator if provided', async () => { - await withUseCache(async ({ cache, testFunction }) => { - const customKeyGenerator = jest.fn().mockReturnValue('custom-key'); - - const customCachedFunction = useCache(testFunction, cache, { - ...cacheOptions, - generateCacheKey: customKeyGenerator, - }); - - await customCachedFunction(); - - expect(customKeyGenerator).toHaveBeenCalledTimes(1); - expect(cache.get).toHaveBeenCalledWith('custom-key'); - }); - }); - }); - - describe('anonymous functions', () => { - it('should handle anonymous functions with a default name', async () => { - await withUseCache(async ({ actualExecutionSpy, cache }) => { - // Anonymous function with no name - const anonymousFunction = async (): Promise => - actualExecutionSpy(); - Object.defineProperty(anonymousFunction, 'name', { value: null }); - - const cachedAnonymousFunction = useCache(anonymousFunction, cache, { - ttlMilliseconds: 1000, - }); - - await cachedAnonymousFunction(); - - expect(cache.get).toHaveBeenCalledWith('anonymousFunction:'); - }); - }); - }); - - describe('function name override', () => { - it('should use the provided function name if given', async () => { - await withUseCache(async ({ testFunction, cache }) => { - const cachedWithCustomName = useCache(testFunction, cache, { - ttlMilliseconds: 1000, - functionName: 'customFunctionName', - }); - - await cachedWithCustomName(); - - expect(cache.get).toHaveBeenCalledWith('customFunctionName:'); - }); - }); - }); - - describe('falsy but valid cache values', () => { - it('should handle falsy but valid cache values (false, 0, empty string)', async () => { - await withUseCache( - async ({ actualExecutionSpy, cache, cachedTestFunction }) => { - // Test with false - cache.get.mockResolvedValue(false); - let result = await cachedTestFunction(); - expect(result).toBe(false); - expect(actualExecutionSpy).not.toHaveBeenCalled(); - - // Test with 0 - cache.get.mockResolvedValue(0); - result = await cachedTestFunction(); - expect(result).toBe(0); - expect(actualExecutionSpy).not.toHaveBeenCalled(); - - // Test with empty string - cache.get.mockResolvedValue(''); - result = await cachedTestFunction(); - expect(result).toBe(''); - expect(actualExecutionSpy).not.toHaveBeenCalled(); - }, - ); - }); - - it('should execute the function when cache returns undefined', async () => { - await withUseCache( - async ({ actualExecutionSpy, cache, cachedTestFunction }) => { - cache.get.mockResolvedValue(undefined); - actualExecutionSpy.mockResolvedValueOnce('test'); - - const result = await cachedTestFunction(); - - expect(result).toBe('test'); - expect(actualExecutionSpy).toHaveBeenCalledTimes(1); - }, - ); - }); - }); -}); diff --git a/packages/tron-wallet-snap/src/caching/useCache.ts b/packages/tron-wallet-snap/src/caching/useCache.ts deleted file mode 100644 index b596ef607..000000000 --- a/packages/tron-wallet-snap/src/caching/useCache.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* eslint-disable no-void */ - -import type { Serializable } from '@metamask/snap-networks-utils'; - -import logger from '../utils/logger'; -import type { ICache } from './ICache'; - -/** - * Options for configuring the caching behavior of a function. - */ -export type CacheOptions = { - /** - * The time to live for the cache in milliseconds. - */ - ttlMilliseconds: number; - /** - * Set this if you want to use a custom function name for the cache key. - */ - functionName?: string; - /** - * Optional function to generate the cache key for the function call. - * Defaults to a function that generates the key based on function name and JSON stringified args separated by colons. - */ - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - generateCacheKey?: (functionName: string, args: any[]) => string; -}; - -/** - * Default function to generate the cache key for a function call. - * - * @param functionName - The name of the function. - * @param args - The arguments of the function call. - * @returns The cache key. - */ -// TODO: Replace `any` with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const defaultGenerateCacheKey = (functionName: string, args: any[]): string => - `${functionName}:${args.map((arg) => JSON.stringify(arg)).join(':')}`; - -/** - * Wraps a function with caching behavior. - * - * @template TArgs - Tuple type representing the arguments of the function. - * @template TResult - The return type of the function, must be Serializable. - * @param fn - The asynchronous function to wrap. Must return a Promise. - * @param cache - The cache instance to use. - * @param options - The caching options. - * @param options.ttlMilliseconds - The time to live for the cache in milliseconds. - * @param options.functionName - The name of the function. - * @param options.generateCacheKey - Optional function to generate the cache key. - * @returns A new asynchronous function with caching behavior. - */ -// TODO: Replace `any` with type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const useCache = ( - fn: (...args: TArgs) => Promise, - cache: ICache, - { ttlMilliseconds, functionName, generateCacheKey }: CacheOptions, -): ((...args: TArgs) => Promise) => { - // Use provided key generator or default, adapting the default to use the function's name - const _generateCacheKey = generateCacheKey ?? defaultGenerateCacheKey; - - // Get the function name for the default key generator, handle anonymous functions - const _functionName = functionName ?? fn.name ?? 'anonymousFunction'; - - return async (...args: TArgs): Promise => { - const cacheKey = _generateCacheKey(_functionName, args); - - // Check if the data is cached - try { - const cached = await cache.get(cacheKey); - // Check explicitly for undefined, as null or other falsy values might be valid cache results - if (cached !== undefined) { - // Type assertion because cache stores Serializable, but we expect TResult - return cached as TResult; - } - } catch (error) { - // Log cache get errors but proceed to execute the function - logger.error(`Cache get error for key "${cacheKey}":`, error); - } - - // Execute the original function - const result = await fn(...args); - - // Cache the result, handle potential errors silently - // We don't await this, allowing it to happen in the background - void cache.set(cacheKey, result, ttlMilliseconds).catch((error) => { - logger.error(`Cache set error for key "${cacheKey}":`, error); - }); - - return result; - }; -}; diff --git a/packages/tron-wallet-snap/src/caching/useCacheUntil.ts b/packages/tron-wallet-snap/src/caching/useCacheUntil.ts index 05abcd951..586af42c6 100644 --- a/packages/tron-wallet-snap/src/caching/useCacheUntil.ts +++ b/packages/tron-wallet-snap/src/caching/useCacheUntil.ts @@ -44,9 +44,9 @@ const defaultGenerateCacheKey = (functionName: string, args: any[]): string => * Wraps an async function with caching behavior where expiry is determined * by the function result itself (dynamic TTL). * - * Unlike `useCache` which uses a fixed TTL, this utility allows the wrapped - * function to specify when its result expires. This is useful for caching - * data that has known invalidation points (e.g., blockchain maintenance periods). + * This utility allows the wrapped function to be cached until a specific time. + * This is useful for caching data that has known invalidation points + * (e.g., blockchain maintenance periods). * * @template TArgs - Tuple type representing the arguments of the function. * @template TResult - The return type of the function, must be Serializable. diff --git a/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.test.ts b/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.test.ts index 4c305ad42..bb26d2ea3 100644 --- a/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.test.ts +++ b/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.test.ts @@ -8,8 +8,6 @@ import { InMemoryCache } from '../../caching/InMemoryCache'; import { KnownCaip19Id } from '../../constants'; import type { ConfigProvider } from '../../services/config'; import { mockLogger } from '../../utils/mockLogger'; -import { MOCK_EXCHANGE_RATES } from './mocks/exchange-rates'; -import { MOCK_HISTORICAL_PRICES } from './mocks/historical-prices'; import { PriceApiClient } from './PriceApiClient'; import type { SpotPrices, VsCurrencyParam } from './types'; @@ -27,9 +25,7 @@ describe('PriceApiClient', () => { baseUrl: 'https://some-mock-url.com', chunkSize: 50, cacheTtlsMilliseconds: { - fiatExchangeRates: 0, spotPrices: 0, - historicalPrices: 0, }, }, }), @@ -45,50 +41,6 @@ describe('PriceApiClient', () => { ); }); - describe('getFiatExchangeRates', () => { - it('fetches fiat exchange rates successfully', async () => { - mockFetch.mockResolvedValueOnce({ - ok: true, - json: jest.fn().mockResolvedValueOnce(MOCK_EXCHANGE_RATES), - }); - - const result = await client.getFiatExchangeRates(); - - expect(mockFetch).toHaveBeenCalledWith( - 'https://some-mock-url.com/v1/exchange-rates/fiat', - ); - expect(result).toStrictEqual(MOCK_EXCHANGE_RATES); - }); - - it('logs and throws when response is not ok', async () => { - mockFetch.mockResolvedValueOnce({ - ok: false, - status: 500, - }); - - await expect(client.getFiatExchangeRates()).rejects.toThrow( - 'HTTP error! status: 500', - ); - expect(mockLogger.error).toHaveBeenCalledWith( - expect.any(Error), - 'Error fetching fiat exchange rates', - ); - }); - - it('logs and throws when fetch fails', async () => { - const mockError = new Error('Network error'); - mockFetch.mockRejectedValueOnce(mockError); - - await expect(client.getFiatExchangeRates()).rejects.toThrow( - 'Network error', - ); - expect(mockLogger.error).toHaveBeenCalledWith( - mockError, - 'Error fetching fiat exchange rates', - ); - }); - }); - describe('getMultipleSpotPrices', () => { const mockResponse: SpotPrices = { [KnownCaip19Id.TrxMainnet]: { @@ -346,9 +298,7 @@ describe('PriceApiClient', () => { baseUrl: 'invalid-url', chunkSize: 50, cacheTtlsMilliseconds: { - fiatExchangeRates: 0, spotPrices: 0, - historicalPrices: 0, }, }, }), @@ -415,59 +365,4 @@ describe('PriceApiClient', () => { ).rejects.toThrow(/Expected/u); }); }); - - describe('getHistoricalPrices', () => { - describe('when the data is not cached', () => { - it('fetches historical prices successfully', async () => { - mockFetch.mockResolvedValueOnce({ - ok: true, - json: jest.fn().mockResolvedValueOnce(MOCK_HISTORICAL_PRICES), - }); - - const cacheSetSpy = jest.spyOn(mockCache, 'set'); - - const result = await client.getHistoricalPrices({ - assetType: KnownCaip19Id.TrxMainnet, - timePeriod: '5d', - from: 123, - to: 456, - vsCurrency: 'usd', - }); - - expect(mockFetch).toHaveBeenCalledWith( - 'https://some-mock-url.com/v3/historical-prices/tron:728126428/slip44:195?timePeriod=5d&from=123&to=456&vsCurrency=usd', - ); - expect(cacheSetSpy).toHaveBeenCalledWith( - 'PriceApiClient:getHistoricalPrices:{"assetType":"tron:728126428/slip44:195","timePeriod":"5d","from":123,"to":456,"vsCurrency":"usd"}', - MOCK_HISTORICAL_PRICES, - 0, - ); - expect(result).toStrictEqual(MOCK_HISTORICAL_PRICES); - }); - }); - - describe('when the data is cached', () => { - it('returns the cached data', async () => { - jest - .spyOn(mockCache, 'get') - .mockResolvedValueOnce(MOCK_HISTORICAL_PRICES); - - const cacheGetSpy = jest.spyOn(mockCache, 'get'); - const cacheSetSpy = jest.spyOn(mockCache, 'set'); - - const result = await client.getHistoricalPrices({ - assetType: KnownCaip19Id.TrxMainnet, - timePeriod: '5d', - from: 123, - to: 456, - vsCurrency: 'usd', - }); - - expect(cacheGetSpy).toHaveBeenCalled(); - expect(mockFetch).not.toHaveBeenCalled(); - expect(result).toStrictEqual(MOCK_HISTORICAL_PRICES); - expect(cacheSetSpy).not.toHaveBeenCalled(); - }); - }); - }); }); diff --git a/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.ts b/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.ts index 404e787a6..3a3bfe752 100644 --- a/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.ts +++ b/packages/tron-wallet-snap/src/clients/price-api/PriceApiClient.ts @@ -8,24 +8,11 @@ import { CaipAssetTypeStruct } from '@metamask/utils'; import { mapKeys } from 'lodash'; import type { ICache } from '../../caching/ICache'; -import { useCache } from '../../caching/useCache'; import { SNAP_OWNED_ASSETS } from '../../constants'; import type { ConfigProvider } from '../../services/config'; import logger from '../../utils/logger'; -import type { - FiatExchangeRatesResponse, - GetHistoricalPricesParams, - GetHistoricalPricesResponse, - SpotPrices, - VsCurrencyParam, -} from './types'; -import { - FiatExchangeRatesResponseStruct, - GetHistoricalPricesParamsStruct, - GetHistoricalPricesResponseStruct, - SpotPricesStruct, - VsCurrencyParamStruct, -} from './types'; +import type { SpotPrices, VsCurrencyParam } from './types'; +import { SpotPricesStruct, VsCurrencyParamStruct } from './types'; export class PriceApiClient { readonly #fetch: typeof globalThis.fetch; @@ -39,9 +26,7 @@ export class PriceApiClient { readonly #cache: ICache; readonly cacheTtlsMilliseconds: { - fiatExchangeRates: number; spotPrices: number; - historicalPrices: number; }; constructor( @@ -64,29 +49,6 @@ export class PriceApiClient { this.#cache = _cache; } - async getFiatExchangeRates(): Promise { - try { - const url = buildUrl({ - baseUrl: this.#baseUrl, - path: '/v1/exchange-rates/fiat', - }); - - const response = await this.#fetch(url); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - assert(data, FiatExchangeRatesResponseStruct); - - return data; - } catch (error) { - this.#logger.error(error, 'Error fetching fiat exchange rates'); - throw error; - } - } - /** * Business logic for `getMultipleSpotPrices`. * @@ -260,68 +222,4 @@ export class PriceApiClient { return this.#getMultipleSpotPrices_CACHE(filteredTokens, vsCurrency); } - - /** - * Business logic for `getHistoricalPrices`. - * - * @param params - The parameters for the request. - * @param params.assetType - The asset type of the token. - * @param params.timePeriod - The time period for the historical prices. - * @param params.from - The start date for the historical prices. - * @param params.to - The end date for the historical prices. - * @param params.vsCurrency - The currency to convert the prices to. - * @returns The historical prices for the token. - */ - async #getHistoricalPrices_INTERNAL( - params: GetHistoricalPricesParams, - ): Promise { - const url = buildUrl({ - baseUrl: this.#baseUrl, - path: '/v3/historical-prices/{assetType}', - pathParams: { - assetType: params.assetType, - }, - queryParams: { - ...(params.timePeriod && { timePeriod: params.timePeriod }), - ...(params.from && { from: params.from.toString() }), - ...(params.to && { to: params.to.toString() }), - ...(params.vsCurrency && { vsCurrency: params.vsCurrency }), - }, - encodePathParams: false, - }); - - const response = await this.#fetch(url); - const historicalPrices = await response.json(); - assert(historicalPrices, GetHistoricalPricesResponseStruct); - - return historicalPrices; - } - - /** - * Get historical prices for a token by calling the Price API. - * It caches the results for 1 hour. - * - * @see https://price.uat-api.cx.metamask.io/docs#/Historical%20Prices/PriceController_getHistoricalPricesByCaipAssetId - * @param params - The parameters for the request. - * @param params.assetType - The asset type of the token. - * @param params.timePeriod - The time period for the historical prices. - * @param params.from - The start date for the historical prices. - * @param params.to - The end date for the historical prices. - * @param params.vsCurrency - The currency to convert the prices to. - * @returns The historical prices for the token. - */ - async getHistoricalPrices( - params: GetHistoricalPricesParams, - ): Promise { - assert(params, GetHistoricalPricesParamsStruct); - - return useCache( - this.#getHistoricalPrices_INTERNAL.bind(this), - this.#cache, - { - functionName: 'PriceApiClient:getHistoricalPrices', - ttlMilliseconds: this.cacheTtlsMilliseconds.historicalPrices, - }, - )(params); - } } diff --git a/packages/tron-wallet-snap/src/clients/price-api/mocks/exchange-rates.ts b/packages/tron-wallet-snap/src/clients/price-api/mocks/exchange-rates.ts deleted file mode 100644 index dc9d2dfce..000000000 --- a/packages/tron-wallet-snap/src/clients/price-api/mocks/exchange-rates.ts +++ /dev/null @@ -1,476 +0,0 @@ -import type { ExchangeRate, Ticker } from '../types'; - -/** - * HEADS UP! Changing this mock MUST involve changing the spot prices mock too! - * Their values are interdependent and essential for the TokenPricesService tests. - */ -export const MOCK_EXCHANGE_RATES: Record = { - btc: { - name: 'Bitcoin', - ticker: 'btc', - value: 0.000009225522122806664, - currencyType: 'crypto', - }, - eth: { - name: 'Ether', - ticker: 'eth', - value: 0.0004032198954215109, - currencyType: 'crypto', - }, - ltc: { - name: 'Litecoin', - ticker: 'ltc', - value: 0.011656225789635273, - currencyType: 'crypto', - }, - bch: { - name: 'Bitcoin Cash', - ticker: 'bch', - value: 0.001982942950598187, - currencyType: 'crypto', - }, - bnb: { - name: 'Binance Coin', - ticker: 'bnb', - value: 0.0015156056764231698, - currencyType: 'crypto', - }, - eos: { - name: 'EOS', - ticker: 'eos', - value: 2.056880058128908, - currencyType: 'crypto', - }, - xrp: { - name: 'XRP', - ticker: 'xrp', - value: 0.4540842119866674, - currencyType: 'crypto', - }, - xlm: { - name: 'Lumens', - ticker: 'xlm', - value: 4.29161071887215, - currencyType: 'crypto', - }, - link: { - name: 'Chainlink', - ticker: 'link', - value: 0.07546219704388624, - currencyType: 'crypto', - }, - dot: { - name: 'Polkadot', - ticker: 'dot', - value: 0.29389602831032285, - currencyType: 'crypto', - }, - yfi: { - name: 'Yearn.finance', - ticker: 'yfi', - value: 0.00019925282680837832, - currencyType: 'crypto', - }, - usd: { - name: 'US Dollar', - ticker: 'usd', - value: 1, - currencyType: 'fiat', - }, - aed: { - name: 'United Arab Emirates Dirham', - ticker: 'aed', - value: 3.6730349953852555, - currencyType: 'fiat', - }, - ars: { - name: 'Argentine Peso', - ticker: 'ars', - value: 1206.0000013561519, - currencyType: 'fiat', - }, - aud: { - name: 'Australian Dollar', - ticker: 'aud', - value: 1.5232439935923583, - currencyType: 'fiat', - }, - bdt: { - name: 'Bangladeshi Taka', - ticker: 'bdt', - value: 122.29205113607277, - currencyType: 'fiat', - }, - bhd: { - name: 'Bahraini Dinar', - ticker: 'bhd', - value: 0.3769909979846017, - currencyType: 'fiat', - }, - bmd: { - name: 'Bermudian Dollar', - ticker: 'bmd', - value: 1, - currencyType: 'fiat', - }, - brl: { - name: 'Brazil Real', - ticker: 'brl', - value: 5.446300002410629, - currencyType: 'fiat', - }, - cad: { - name: 'Canadian Dollar', - ticker: 'cad', - value: 1.3640219988479354, - currencyType: 'fiat', - }, - chf: { - name: 'Swiss Franc', - ticker: 'chf', - value: 0.7936309928980179, - currencyType: 'fiat', - }, - clp: { - name: 'Chilean Peso', - ticker: 'clp', - value: 923.830001036303, - currencyType: 'fiat', - }, - cny: { - name: 'Chinese Yuan', - ticker: 'cny', - value: 7.166700000015684, - currencyType: 'fiat', - }, - czk: { - name: 'Czech Koruna', - ticker: 'czk', - value: 20.952984017733154, - currencyType: 'fiat', - }, - dkk: { - name: 'Danish Krone', - ticker: 'dkk', - value: 6.339276002611524, - currencyType: 'fiat', - }, - eur: { - name: 'Euro', - ticker: 'eur', - value: 0.8496419976174352, - currencyType: 'fiat', - }, - gbp: { - name: 'British Pound Sterling', - ticker: 'gbp', - value: 0.7356629966217338, - currencyType: 'fiat', - }, - gel: { - name: 'Georgian Lari', - ticker: 'gel', - value: 2.719999997416854, - currencyType: 'fiat', - }, - hkd: { - name: 'Hong Kong Dollar', - ticker: 'hkd', - value: 7.84986500616371, - currencyType: 'fiat', - }, - huf: { - name: 'Hungarian Forint', - ticker: 'huf', - value: 340.2474533753413, - currencyType: 'fiat', - }, - idr: { - name: 'Indonesian Rupiah', - ticker: 'idr', - value: 16212.776418318166, - currencyType: 'fiat', - }, - ils: { - name: 'Israeli New Shekel', - ticker: 'ils', - value: 3.3717049952207647, - currencyType: 'fiat', - }, - inr: { - name: 'Indian Rupee', - ticker: 'inr', - value: 85.59833408842695, - currencyType: 'fiat', - }, - jpy: { - name: 'Japanese Yen', - ticker: 'jpy', - value: 143.9902001614485, - currencyType: 'fiat', - }, - krw: { - name: 'South Korean Won', - ticker: 'krw', - value: 1359.3506945328236, - currencyType: 'fiat', - }, - kwd: { - name: 'Kuwaiti Dinar', - ticker: 'kwd', - value: 0.30529199289535164, - currencyType: 'fiat', - }, - lkr: { - name: 'Sri Lankan Rupee', - ticker: 'lkr', - value: 299.9010793298127, - currencyType: 'fiat', - }, - mmk: { - name: 'Burmese Kyat', - ticker: 'mmk', - value: 2098.0000023617336, - currencyType: 'fiat', - }, - mxn: { - name: 'Mexican Peso', - ticker: 'mxn', - value: 18.77348001704397, - currencyType: 'fiat', - }, - myr: { - name: 'Malaysian Ringgit', - ticker: 'myr', - value: 4.228999997038608, - currencyType: 'fiat', - }, - ngn: { - name: 'Nigerian Naira', - ticker: 'ngn', - value: 1532.4200017290473, - currencyType: 'fiat', - }, - nok: { - name: 'Norwegian Krone', - ticker: 'nok', - value: 10.109898008254978, - currencyType: 'fiat', - }, - nzd: { - name: 'New Zealand Dollar', - ticker: 'nzd', - value: 1.6478669960903807, - currencyType: 'fiat', - }, - php: { - name: 'Philippine Peso', - ticker: 'php', - value: 56.376001062558736, - currencyType: 'fiat', - }, - pkr: { - name: 'Pakistani Rupee', - ticker: 'pkr', - value: 285.2245003132019, - currencyType: 'fiat', - }, - pln: { - name: 'Polish Zloty', - ticker: 'pln', - value: 3.625871995197858, - currencyType: 'fiat', - }, - rub: { - name: 'Russian Ruble', - ticker: 'rub', - value: 78.79997408366326, - currencyType: 'fiat', - }, - sar: { - name: 'Saudi Riyal', - ticker: 'sar', - value: 3.7501600005365567, - currencyType: 'fiat', - }, - sek: { - name: 'Swedish Krona', - ticker: 'sek', - value: 9.55167101005786, - currencyType: 'fiat', - }, - sgd: { - name: 'Singapore Dollar', - ticker: 'sgd', - value: 1.2739619998345126, - currencyType: 'fiat', - }, - thb: { - name: 'Thai Baht', - ticker: 'thb', - value: 32.40583303378832, - currencyType: 'fiat', - }, - try: { - name: 'Turkish Lira', - ticker: 'try', - value: 39.788298041452094, - currencyType: 'fiat', - }, - twd: { - name: 'New Taiwan Dollar', - ticker: 'twd', - value: 29.018999031034188, - currencyType: 'fiat', - }, - uah: { - name: 'Ukrainian hryvnia', - ticker: 'uah', - value: 41.75092204711494, - currencyType: 'fiat', - }, - vef: { - name: 'Venezuelan bolívar fuerte', - ticker: 'vef', - value: 0.10012999775478468, - currencyType: 'fiat', - }, - vnd: { - name: 'Vietnamese đồng', - ticker: 'vnd', - value: 26167.73565956473, - currencyType: 'fiat', - }, - zar: { - name: 'South African Rand', - ticker: 'zar', - value: 17.638879012711193, - currencyType: 'fiat', - }, - xdr: { - name: 'IMF Special Drawing Rights', - ticker: 'xdr', - value: 0.6961849947454656, - currencyType: 'fiat', - }, - xag: { - name: 'Silver - Troy Ounce', - ticker: 'xag', - value: 0.02745114996087133, - currencyType: 'commodity', - }, - xau: { - name: 'Gold - Troy Ounce', - ticker: 'xau', - value: 0.0002992943887080938, - currencyType: 'commodity', - }, - bits: { - name: 'Bits', - ticker: 'bits', - value: 9.225522122806664, - currencyType: 'crypto', - }, - sats: { - name: 'Satoshi', - ticker: 'sats', - value: 922.5522122806664, - currencyType: 'crypto', - }, - cop: { - name: 'Colombian Peso', - ticker: 'cop', - value: 4020.329999998432, - currencyType: 'fiat', - }, - kes: { - name: 'Kenyan Shilling', - ticker: 'kes', - value: 129.20000000184513, - currencyType: 'fiat', - }, - ron: { - name: 'Romanian Leu', - ticker: 'ron', - value: 4.302400003896861, - currencyType: 'fiat', - }, - dop: { - name: 'Dominican Peso', - ticker: 'dop', - value: 59.421077000552856, - currencyType: 'fiat', - }, - crc: { - name: 'Costa Rican Colón', - ticker: 'crc', - value: 505.1511230011281, - currencyType: 'fiat', - }, - hnl: { - name: 'Honduran Lempira', - ticker: 'hnl', - value: 26.133209998558144, - currencyType: 'fiat', - }, - zmw: { - name: 'Zambian Kwacha', - ticker: 'zmw', - value: 24.02423300185325, - currencyType: 'fiat', - }, - svc: { - name: 'Salvadoran Colón', - ticker: 'svc', - value: 8.749590998008589, - currencyType: 'fiat', - }, - bam: { - name: 'Bosnia and Herzegovina Convertible Mark', - ticker: 'bam', - value: 1.6618870036093658, - currencyType: 'fiat', - }, - pen: { - name: 'Peruvian Sol', - ticker: 'pen', - value: 3.5611860013883123, - currencyType: 'fiat', - }, - gtq: { - name: 'Guatemalan Quetzal', - ticker: 'gtq', - value: 7.688288003161476, - currencyType: 'fiat', - }, - lbp: { - name: 'Lebanese Pound', - ticker: 'lbp', - value: 89577.29288500333, - currencyType: 'fiat', - }, - amd: { - name: 'Armenian Dram', - ticker: 'amd', - value: 384.5100000000923, - currencyType: 'fiat', - }, - sol: { - name: 'Solana', - ticker: 'sol', - value: 0.006629188747026665, - currencyType: 'crypto', - }, - sei: { - name: 'Sei Network', - ticker: 'sei', - value: 3.571422841670739, - currencyType: 'crypto', - }, - sonic: { - name: 'Sonic', - ticker: 'sonic', - value: 3.0932878113426843, - currencyType: 'crypto', - }, -}; diff --git a/packages/tron-wallet-snap/src/clients/price-api/mocks/historical-prices.ts b/packages/tron-wallet-snap/src/clients/price-api/mocks/historical-prices.ts deleted file mode 100644 index c49401ef5..000000000 --- a/packages/tron-wallet-snap/src/clients/price-api/mocks/historical-prices.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const MOCK_HISTORICAL_PRICES = { - prices: [ - [1740927906629, 0.4118878563926736], - [1740931479807, 0.42205009065536164], - [1740935079843, 0.45470438113431433], - ], - marketCaps: [ - [1740927906629, 1817840725.6040797], - [1740931479807, 1868369182.2913468], - [1740935079843, 2012074624.0219033], - ], - totalVolumes: [ - [1740927906629, 120486002.56343293], - [1740931479807, 147850728.76918542], - [1740935079843, 220405205.04882324], - ], -}; diff --git a/packages/tron-wallet-snap/src/clients/price-api/types.ts b/packages/tron-wallet-snap/src/clients/price-api/types.ts index 45c212d28..110fc2075 100644 --- a/packages/tron-wallet-snap/src/clients/price-api/types.ts +++ b/packages/tron-wallet-snap/src/clients/price-api/types.ts @@ -1,17 +1,13 @@ import type { Infer } from '@metamask/superstruct'; import { - array, boolean, enums, min, nullable, number, - object, optional, - pattern, record, string, - tuple, type as typeStruct, union, } from '@metamask/superstruct'; @@ -117,33 +113,6 @@ export const TickerStruct = union([ export type Ticker = Infer; -/** - * Struct for validating exchange rate data from the API. - * Includes bounds validation to prevent malicious data injection. - */ -export const ExchangeRateStruct = object({ - name: string(), - ticker: TickerStruct, - value: min(number(), 0), - currencyType: enums(['fiat', 'crypto', 'commodity']), -}); - -export type ExchangeRate = Infer; - -/** - * Struct for validating the fiat exchange rates response. - * Maps ticker symbols to their exchange rate data. - * Despite the endpoint name, the response includes all exchange rates (crypto, fiat, commodity). - */ -export const FiatExchangeRatesResponseStruct = record( - TickerStruct, - ExchangeRateStruct, -); - -export type FiatExchangeRatesResponse = Infer< - typeof FiatExchangeRatesResponseStruct ->; - /** * The structure of the spot price response from the Price API as described in * [this file](https://github.com/consensys-vertical-apps/va-mmcx-price-api/blob/main/src/types/price.ts#L46-L71). @@ -218,32 +187,3 @@ export type SpotPrices = Infer; // We create aliases here for clarity. export const VsCurrencyParamStruct = TickerStruct; export type VsCurrencyParam = Infer; - -export const GetHistoricalPricesParamsStruct = object({ - assetType: CaipAssetTypeStruct, - timePeriod: optional(pattern(string(), /^[1-9][0-9]*[dmy]$/u)), // Supports days, months, years - from: optional(min(number(), 0)), - to: optional(min(number(), 0)), - vsCurrency: optional(VsCurrencyParamStruct), -}); - -export type GetHistoricalPricesParams = Infer< - typeof GetHistoricalPricesParamsStruct ->; - -export const GetHistoricalPricesResponseStruct = object({ - prices: array(tuple([number(), number()])), - marketCaps: array(tuple([number(), number()])), - totalVolumes: array(tuple([number(), number()])), -}); - -export type GetHistoricalPricesResponse = Infer< - typeof GetHistoricalPricesResponseStruct ->; - -export const GET_HISTORICAL_PRICES_RESPONSE_NULL_OBJECT: GetHistoricalPricesResponse = - { - prices: [], - marketCaps: [], - totalVolumes: [], - }; diff --git a/packages/tron-wallet-snap/src/context.ts b/packages/tron-wallet-snap/src/context.ts index c400f1444..6eaa5b5a5 100644 --- a/packages/tron-wallet-snap/src/context.ts +++ b/packages/tron-wallet-snap/src/context.ts @@ -17,7 +17,6 @@ import { TokenApiClient } from './clients/token-api/TokenApiClient'; import { TronHttpClient } from './clients/tron-http/TronHttpClient'; import { TrongridApiClient } from './clients/trongrid/TrongridApiClient'; import { TronWebFactory } from './clients/tronweb/TronWebFactory'; -import { AssetsHandler } from './handlers/assets/assets'; import { ClientRequestHandler } from './handlers/clientRequest/clientRequest'; import { CronHandler } from './handlers/cronjob/cronjob'; import { KeyringHandler } from './handlers/keyring/keyring'; @@ -54,7 +53,7 @@ import logger, { noOpLogger } from './utils/logger'; * 1. Core services (ConfigProvider, State, Connection) * 2. Repositories (AssetsRepository, TransactionsRepository, AccountsRepository) * 3. Business services (AssetsService, TransactionsService, AccountsService) - * 4. Handlers (AssetsHandler, CronHandler, KeyringHandler, RpcHandler, UserInputHandler) + * 4. Handlers (CronHandler, KeyringHandler, RpcHandler, UserInputHandler) */ export const configProvider = new ConfigProvider(); @@ -63,7 +62,6 @@ const state = new State({ defaultState: { keyringAccounts: {}, assets: {}, - tokenPrices: {}, transactions: {}, mapInterfaceNameToId: {}, }, @@ -124,7 +122,6 @@ const snapAssetsAdapter = new SnapAssetsAdapter({ priceApiClient, tokenApiClient, snapClient, - configProvider, }); const coreAssetsAdapter = new CoreAssetsAdapter({ getAccountAssetByID: assetsProvider.getAccountAssetByID.bind(assetsProvider), @@ -216,10 +213,6 @@ const confirmationHandler = new ConfirmationHandler({ /** * Handlers */ -const assetsHandler = new AssetsHandler({ - logger, - assetsService, -}); const clientRequestHandler = new ClientRequestHandler({ logger, snapClient, @@ -290,7 +283,6 @@ export type SnapExecutionContext = { /** * Handlers */ - assetsHandler: AssetsHandler; cronHandler: CronHandler; clientRequestHandler: ClientRequestHandler; keyringHandler: KeyringHandler; @@ -325,7 +317,6 @@ const snapContext: SnapExecutionContext = { /** * Handlers */ - assetsHandler, clientRequestHandler, cronHandler, keyringHandler, @@ -337,7 +328,6 @@ export { /** * Handlers */ - assetsHandler, clientRequestHandler, cronHandler, keyringHandler, diff --git a/packages/tron-wallet-snap/src/handlers/assets/assets.ts b/packages/tron-wallet-snap/src/handlers/assets/assets.ts deleted file mode 100644 index 5a35db6c2..000000000 --- a/packages/tron-wallet-snap/src/handlers/assets/assets.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { Logger } from '@metamask/snap-networks-utils'; -import type { - OnAssetHistoricalPriceArguments, - OnAssetHistoricalPriceResponse, - OnAssetsConversionArguments, - OnAssetsConversionResponse, - OnAssetsLookupArguments, - OnAssetsLookupResponse, - OnAssetsMarketDataArguments, - OnAssetsMarketDataResponse, -} from '@metamask/snaps-sdk'; - -import type { AssetsService } from '../../services/assets/AssetsService'; - -export class AssetsHandler { - readonly #logger: Logger; - - readonly #assetsService: AssetsService; - - constructor({ - logger, - assetsService, - }: { - logger: Logger; - assetsService: AssetsService; - }) { - this.#logger = logger.withPrefix('[🪙 AssetsHandler]'); - this.#assetsService = assetsService; - } - - async onAssetHistoricalPrice( - params: OnAssetHistoricalPriceArguments, - ): Promise { - this.#logger.log('[📈 onAssetHistoricalPrice]', params); - - const { from, to } = params; - - const historicalPrice = await this.#assetsService.getHistoricalPrice( - from, - to, - ); - - return { - historicalPrice, - }; - } - - async onAssetsConversion( - params: OnAssetsConversionArguments, - ): Promise { - this.#logger.log('[💱 onAssetsConversion]'); - - const { conversions } = params; - - const conversionRates = - await this.#assetsService.getMultipleTokenConversions(conversions); - - return { - conversionRates, - }; - } - - async onAssetsLookup( - params: OnAssetsLookupArguments, - ): Promise { - const assets = await this.#assetsService.getAssetsMetadata(params.assets); - - return { assets }; - } - - async onAssetsMarketData( - params: OnAssetsMarketDataArguments, - ): Promise { - const marketData = await this.#assetsService.getMultipleTokensMarketData( - params.assets, - ); - - return { marketData }; - } -} diff --git a/packages/tron-wallet-snap/src/index.ts b/packages/tron-wallet-snap/src/index.ts index 80fb5f84d..4493952cd 100644 --- a/packages/tron-wallet-snap/src/index.ts +++ b/packages/tron-wallet-snap/src/index.ts @@ -1,8 +1,4 @@ import type { - OnAssetHistoricalPriceHandler, - OnAssetsConversionHandler, - OnAssetsLookupHandler, - OnAssetsMarketDataHandler, OnClientRequestHandler, OnCronjobHandler, OnKeyringRequestHandler, @@ -11,7 +7,6 @@ import type { } from '@metamask/snaps-sdk'; import { - assetsHandler, clientRequestHandler, cronHandler, keyringHandler, @@ -24,26 +19,6 @@ import { withCatchAndThrowSnapError } from './utils/errors'; * Register all handlers */ -export const onAssetHistoricalPrice: OnAssetHistoricalPriceHandler = async ( - args, -) => - withCatchAndThrowSnapError(async () => - assetsHandler.onAssetHistoricalPrice(args), - ); - -export const onAssetsConversion: OnAssetsConversionHandler = async (args) => - withCatchAndThrowSnapError(async () => - assetsHandler.onAssetsConversion(args), - ); - -export const onAssetsLookup: OnAssetsLookupHandler = async (args) => - withCatchAndThrowSnapError(async () => assetsHandler.onAssetsLookup(args)); - -export const onAssetsMarketData: OnAssetsMarketDataHandler = async (args) => - withCatchAndThrowSnapError(async () => - assetsHandler.onAssetsMarketData(args), - ); - export const onClientRequest: OnClientRequestHandler = async ({ request }) => withCatchAndThrowSnapError(async () => clientRequestHandler.handle(request)); diff --git a/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.test.ts b/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.test.ts index b53cc0b0e..7224b8d79 100644 --- a/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.test.ts +++ b/packages/tron-wallet-snap/src/services/accounts/AccountsRepository.test.ts @@ -17,7 +17,6 @@ function createEmptyState( return new InMemoryState({ keyringAccounts, assets: {}, - tokenPrices: {}, transactions: {}, mapInterfaceNameToId: {}, }); diff --git a/packages/tron-wallet-snap/src/services/accounts/AccountsService.test.ts b/packages/tron-wallet-snap/src/services/accounts/AccountsService.test.ts index cbe0fe083..3c7084045 100644 --- a/packages/tron-wallet-snap/src/services/accounts/AccountsService.test.ts +++ b/packages/tron-wallet-snap/src/services/accounts/AccountsService.test.ts @@ -59,9 +59,7 @@ const MOCK_CONFIG: Config = { baseUrl: '', chunkSize: 0, cacheTtlsMilliseconds: { - fiatExchangeRates: 0, spotPrices: 0, - historicalPrices: 0, }, }, tokenApi: { baseUrl: '', chunkSize: 0 }, diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsRepository.test.ts b/packages/tron-wallet-snap/src/services/assets/AssetsRepository.test.ts index 967e647f8..25fb54dcc 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsRepository.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsRepository.test.ts @@ -122,7 +122,6 @@ describe('AssetsRepository', () => { ): UnencryptedStateValue => ({ keyringAccounts: {}, assets, - tokenPrices: {}, transactions: {}, mapInterfaceNameToId: {}, }); diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts index 000a84306..57a36a90f 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.test.ts @@ -11,7 +11,6 @@ import { RemoteFeatureFlagsProvider, } from '@metamask/snap-networks-utils'; -import { MOCK_EXCHANGE_RATES } from '../../clients/price-api/mocks/exchange-rates'; import type { PriceApiClient } from '../../clients/price-api/PriceApiClient'; import type { SpotPrices } from '../../clients/price-api/types'; import type { SnapClient } from '../../clients/snap/SnapClient'; @@ -24,7 +23,6 @@ import { KnownCaip19Id, Network, SNAP_OWNED_ASSETS } from '../../constants'; import type { AssetEntity } from '../../entities/assets'; import type { CoreMessengerCaller } from '../../types/core-messenger'; import { mockLogger } from '../../utils/mockLogger'; -import type { ConfigProvider } from '../config'; import { CoreAssetsAdapter } from './adapters/CoreAssetsAdapter'; import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; import type { AssetsRepository } from './AssetsRepository'; @@ -39,32 +37,6 @@ type MockState = { setKeyWith: jest.Mock; }; -jest.mock('../../context', () => ({ - configProvider: { - get(): { - priceApi: { - cacheTtlsMilliseconds: { - fiatExchangeRates: number; - spotPrices: number; - historicalPrices: number; - }; - }; - activeNetworks: never[]; - } { - return { - priceApi: { - cacheTtlsMilliseconds: { - fiatExchangeRates: 3600000, - spotPrices: 3600000, - historicalPrices: 3600000, - }, - }, - activeNetworks: [], - }; - }, - }, -})); - jest.mock('@metamask/keyring-snap-sdk', () => ({ emitSnapKeyringEvent: jest.fn(), })); @@ -258,10 +230,7 @@ type WithAssetsServiceCallback = (payload: { Pick >; mockPriceApiClient: jest.Mocked< - Pick< - PriceApiClient, - 'getFiatExchangeRates' | 'getHistoricalPrices' | 'getMultipleSpotPrices' - > + Pick >; mockTokenApiClient: jest.Mocked>; mockSnapClient: jest.Mocked>; @@ -319,13 +288,8 @@ async function withAssetsService( }; const mockPriceApiClient: jest.Mocked< - Pick< - PriceApiClient, - 'getFiatExchangeRates' | 'getHistoricalPrices' | 'getMultipleSpotPrices' - > + Pick > = { - getFiatExchangeRates: jest.fn(), - getHistoricalPrices: jest.fn(), getMultipleSpotPrices: jest.fn().mockResolvedValue({}), }; @@ -369,19 +333,6 @@ async function withAssetsService( messenger: mockCoreMessenger as never, }); - const mockConfigProvider: jest.Mocked> = { - get: jest.fn().mockReturnValue({ - priceApi: { - cacheTtlsMilliseconds: { - fiatExchangeRates: 3600000, - spotPrices: 3600000, - historicalPrices: 3600000, - }, - }, - activeNetworks: [], - }), - }; - const snapAdapter = new SnapAssetsAdapter({ logger: mockLogger, assetsRepository: mockAssetsRepository as never, @@ -391,7 +342,6 @@ async function withAssetsService( priceApiClient: mockPriceApiClient as never, tokenApiClient: mockTokenApiClient as never, snapClient: mockSnapClient as never, - configProvider: mockConfigProvider as never, }); const coreAdapter = new CoreAssetsAdapter({ getAccountAssetByID: @@ -450,8 +400,9 @@ describe('AssetsService', () => { trc20Balances, ); - const trc20AssetId = - `${String(Network.Mainnet)}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` as const; + const trc20AssetId = `${String( + Network.Mainnet, + )}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` as const; mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue( createSpotPrices({ [trc20AssetId]: { id: trc20AssetId, price: 1.0 }, @@ -577,8 +528,9 @@ describe('AssetsService', () => { trc20Balances, ); - const trc20AssetId = - `${String(Network.Mainnet)}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` as const; + const trc20AssetId = `${String( + Network.Mainnet, + )}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` as const; mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue( createSpotPrices({ [trc20AssetId]: { id: trc20AssetId, price: 1.0 }, @@ -1468,25 +1420,6 @@ describe('AssetsService', () => { }); }); - describe('getHistoricalPrice', () => { - it('tracks historical price errors', async () => { - await withAssetsService( - async ({ assetsService, mockSnapClient, mockPriceApiClient }) => { - const error = new Error('Price error'); - - mockPriceApiClient.getHistoricalPrices.mockRejectedValue(error); - - await assetsService.getHistoricalPrice( - KnownCaip19Id.TrxMainnet, - 'tron:728126428/slip44:usd', - ); - - expect(mockSnapClient.trackError).toHaveBeenCalledWith(error); - }, - ); - }); - }); - describe('saveMany', () => { it('does not remove energy and bandwidth assets even when they have zero amounts', async () => { await withAssetsService( @@ -2841,50 +2774,6 @@ describe('AssetsService', () => { }); }); - describe('getAssetsMetadata', () => { - it('resolves metadata for native, protocol, and token asset types', async () => { - await withAssetsService(async ({ assetsService, mockTokenApiClient }) => { - const trc20 = - `${Network.Mainnet}/trc20:TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` as TokenCaipAssetType; - const trc10 = `${Network.Mainnet}/trc10:1002000` as TokenCaipAssetType; - - mockTokenApiClient.getTokensMetadata.mockResolvedValue({ - [trc20]: { - fungible: { symbol: 'USDT', name: 'Tether', decimals: 6 }, - }, - [trc10]: { - fungible: { symbol: 'T', name: 'Token', decimals: 0 }, - }, - } as never); - - const assetTypes = [ - KnownCaip19Id.TrxMainnet, - KnownCaip19Id.TrxStakedForBandwidthMainnet, - KnownCaip19Id.TrxStakedForEnergyMainnet, - KnownCaip19Id.TrxReadyForWithdrawalMainnet, - KnownCaip19Id.TrxInLockPeriodMainnet, - KnownCaip19Id.TrxStakingRewardsMainnet, - KnownCaip19Id.EnergyMainnet, - KnownCaip19Id.MaximumEnergyMainnet, - KnownCaip19Id.BandwidthMainnet, - KnownCaip19Id.MaximumBandwidthMainnet, - trc10, - trc20, - ]; - - const metadata = await assetsService.getAssetsMetadata(assetTypes); - - expect(metadata[KnownCaip19Id.TrxMainnet]?.symbol).toBe('TRX'); - expect(metadata[KnownCaip19Id.EnergyMainnet]?.symbol).toBe('ENERGY'); - expect(metadata[trc20]?.fungible?.symbol).toBe('USDT'); - expect(mockTokenApiClient.getTokensMetadata).toHaveBeenCalledWith([ - trc10, - trc20, - ]); - }); - }); - }); - describe('assets migration', () => { const accountId = mockAccount.id; const fungibleAssetId = KnownCaip19Id.TrxMainnet; @@ -3142,9 +3031,9 @@ describe('AssetsService', () => { }); describe('facade delegation', () => { - it('delegates static helpers and empty batch reads to SnapAssetsAdapter', async () => { + it('delegates empty batch reads to SnapAssetsAdapter', async () => { await withAssetsService( - async ({ assetsService, mockAssetsRepository, mockPriceApiClient }) => { + async ({ assetsService, mockAssetsRepository }) => { const asset: AssetEntity = { iconUrl: '', assetType: KnownCaip19Id.TrxMainnet, @@ -3159,33 +3048,10 @@ describe('AssetsService', () => { mockAssetsRepository.getByAccountIdAndAssetTypes.mockResolvedValue([ asset, ]); - mockPriceApiClient.getFiatExchangeRates.mockResolvedValue( - MOCK_EXCHANGE_RATES, - ); - mockPriceApiClient.getMultipleSpotPrices.mockResolvedValue( - createSpotPrices({ - [KnownCaip19Id.TrxMainnet]: { - id: KnownCaip19Id.TrxMainnet, - price: 1, - }, - }), - ); - expect(AssetsService.isFiat('eip155:1/erc20:0x0')).toBe(false); - expect(AssetsService.isFiat('swift:0/iso4217:usd')).toBe(true); - expect(AssetsService.hasChanged(asset, [])).toBe(true); - expect(AssetsService.hasChanged(asset, [asset])).toBe(false); expect( await assetsService.getAccountAssetsByIDs(mockAccount.id, []), ).toStrictEqual([]); - expect( - await assetsService.getMultipleTokensMarketData([ - { - asset: KnownCaip19Id.TrxMainnet, - unit: 'swift:0/iso4217:usd', - }, - ]), - ).toBeDefined(); }, ); }); diff --git a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts index 1df8c8eb3..0b43b10c3 100644 --- a/packages/tron-wallet-snap/src/services/assets/AssetsService.ts +++ b/packages/tron-wallet-snap/src/services/assets/AssetsService.ts @@ -6,13 +6,6 @@ import { } from '@metamask/assets-controller'; import type { KeyringAccount } from '@metamask/keyring-api'; import type { RemoteFeatureFlagsProvider } from '@metamask/snap-networks-utils'; -import type { - AssetConversion, - AssetMetadata, - FungibleAssetMarketData, - HistoricalPriceIntervals, -} from '@metamask/snaps-sdk'; -import type { CaipAssetType } from '@metamask/utils'; import type { Network } from '../../constants'; import type { AssetEntity } from '../../entities/assets'; @@ -32,8 +25,6 @@ export class AssetsService { readonly #remoteFeatureFlagsProvider: RemoteFeatureFlagsProvider; - readonly cacheTtlsMilliseconds: SnapAssetsAdapter['cacheTtlsMilliseconds']; - constructor({ snapAdapter, coreAdapter, @@ -46,7 +37,6 @@ export class AssetsService { this.#snapAdapter = snapAdapter; this.#coreAdapter = coreAdapter; this.#remoteFeatureFlagsProvider = remoteFeatureFlagsProvider; - this.cacheTtlsMilliseconds = this.#snapAdapter.cacheTtlsMilliseconds; } async #shouldReturnAssetsFromCore(): Promise { @@ -59,14 +49,6 @@ export class AssetsService { return result; } - static isFiat(caipAssetId: CaipAssetType): boolean { - return SnapAssetsAdapter.isFiat(caipAssetId); - } - - static hasChanged(asset: AssetEntity, assetsLookup: AssetEntity[]): boolean { - return SnapAssetsAdapter.hasChanged(asset, assetsLookup); - } - async getAccountAssetsByIDs( accountId: string, assetIds: string[], @@ -117,12 +99,6 @@ export class AssetsService { return this.#snapAdapter.fetchAssetsAndBalancesForAccount(scope, account); } - async getAssetsMetadata( - assetTypes: CaipAssetType[], - ): Promise> { - return this.#snapAdapter.getAssetsMetadata(assetTypes); - } - async saveMany(assets: AssetEntity[]): Promise { if (await this.#shouldReturnAssetsFromCore()) { return this.#coreAdapter.saveMany(assets); @@ -131,10 +107,6 @@ export class AssetsService { return this.#snapAdapter.saveMany(assets); } - async getAll(): Promise { - return this.#snapAdapter.getAll(); - } - async getAccountAssets(accountId: string): Promise { if (await this.#shouldReturnAssetsFromCore()) { return this.#coreAdapter.getAccountAssets(accountId); @@ -142,34 +114,4 @@ export class AssetsService { return this.#snapAdapter.getAccountAssets(accountId); } - - async getMultipleTokenConversions( - conversions: { from: CaipAssetType; to: CaipAssetType }[], - ): Promise< - Record> - > { - return this.#snapAdapter.getMultipleTokenConversions(conversions); - } - - async getMultipleTokensMarketData( - assets: { - asset: CaipAssetType; - unit: CaipAssetType; - }[], - ): Promise< - Record> - > { - return this.#snapAdapter.getMultipleTokensMarketData(assets); - } - - async getHistoricalPrice( - from: CaipAssetType, - to: CaipAssetType, - ): Promise<{ - intervals: HistoricalPriceIntervals; - updateTime: number; - expirationTime?: number; - }> { - return this.#snapAdapter.getHistoricalPrice(from, to); - } } diff --git a/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.test.ts b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.test.ts index f24274971..e2b1b5165 100644 --- a/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/adapters/CoreAssetsAdapter.test.ts @@ -11,7 +11,6 @@ import { } from '../../../clients/trongrid/errors'; import { KnownCaip19Id, Network } from '../../../constants'; import type { AssetEntity } from '../../../entities/assets'; -import { getSnapOwnedAssetIdsForScope } from '../utils/isSnapOwnedAsset'; import { CoreAssetsAdapter } from './CoreAssetsAdapter'; jest.mock('@metamask/keyring-snap-sdk', () => ({ @@ -356,9 +355,20 @@ describe('CoreAssetsAdapter', () => { ); const assetTypes = assets.map((asset) => asset.assetType); + const expectedAssetTypes = [ + KnownCaip19Id.TrxStakedForBandwidthMainnet, + KnownCaip19Id.TrxStakedForEnergyMainnet, + KnownCaip19Id.TrxReadyForWithdrawalMainnet, + KnownCaip19Id.TrxStakingRewardsMainnet, + KnownCaip19Id.TrxInLockPeriodMainnet, + KnownCaip19Id.BandwidthMainnet, + KnownCaip19Id.MaximumBandwidthMainnet, + KnownCaip19Id.EnergyMainnet, + KnownCaip19Id.MaximumEnergyMainnet, + ]; expect(assetTypes).toHaveLength(9); expect([...assetTypes].sort()).toStrictEqual( - [...getSnapOwnedAssetIdsForScope(Network.Mainnet)].sort(), + [...expectedAssetTypes].sort(), ); expect(assetTypes).not.toContain(KnownCaip19Id.TrxMainnet); expect(assets.every((asset) => asset.rawAmount === '0')).toBe(true); diff --git a/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts b/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts index fc5754404..c9fd16bc8 100644 --- a/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts +++ b/packages/tron-wallet-snap/src/services/assets/adapters/SnapAssetsAdapter.ts @@ -6,29 +6,12 @@ import type { } from '@metamask/keyring-api'; import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; import type { Logger } from '@metamask/snap-networks-utils'; -import type { - AssetConversion, - AssetMetadata, - FungibleAssetMarketData, - FungibleAssetMetadata, - HistoricalPriceIntervals, -} from '@metamask/snaps-sdk'; -import { assert } from '@metamask/superstruct'; +import type { AssetMetadata, FungibleAssetMetadata } from '@metamask/snaps-sdk'; import type { CaipAssetType } from '@metamask/utils'; -import { CaipAssetTypeStruct, parseCaipAssetType } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; -import { pick } from 'lodash'; +import { parseCaipAssetType } from '@metamask/utils'; import type { PriceApiClient } from '../../../clients/price-api/PriceApiClient'; -import type { - FiatTicker, - SpotPrice, - SpotPrices, -} from '../../../clients/price-api/types'; -import { - GET_HISTORICAL_PRICES_RESPONSE_NULL_OBJECT, - VsCurrencyParamStruct, -} from '../../../clients/price-api/types'; +import type { SpotPrices } from '../../../clients/price-api/types'; import type { SnapClient } from '../../../clients/snap/SnapClient'; import type { TokenApiClient } from '../../../clients/token-api/TokenApiClient'; import type { AccountResources } from '../../../clients/tron-http'; @@ -56,7 +39,6 @@ import { } from '../../../constants'; import type { AssetEntity } from '../../../entities/assets'; import { toUiAmount } from '../../../utils/conversion'; -import type { ConfigProvider } from '../../config'; import type { State, UnencryptedStateValue } from '../../state/State'; import type { AssetsRepository } from '../AssetsRepository'; import type { @@ -116,14 +98,6 @@ export class SnapAssetsAdapter { readonly #snapClient: SnapClient; - readonly #configProvider: ConfigProvider; - - readonly cacheTtlsMilliseconds: { - fiatExchangeRates: number; - spotPrices: number; - historicalPrices: number; - }; - constructor({ logger, assetsRepository, @@ -133,7 +107,6 @@ export class SnapAssetsAdapter { priceApiClient, tokenApiClient, snapClient, - configProvider, }: { logger: Logger; assetsRepository: AssetsRepository; @@ -143,7 +116,6 @@ export class SnapAssetsAdapter { priceApiClient: PriceApiClient; tokenApiClient: TokenApiClient; snapClient: SnapClient; - configProvider: ConfigProvider; }) { this.#logger = logger.withPrefix('[🪙 SnapAssetsAdapter]'); this.#assetsRepository = assetsRepository; @@ -153,14 +125,6 @@ export class SnapAssetsAdapter { this.#priceApiClient = priceApiClient; this.#tokenApiClient = tokenApiClient; this.#snapClient = snapClient; - this.#configProvider = configProvider; - - const { cacheTtlsMilliseconds } = this.#configProvider.get().priceApi; - this.cacheTtlsMilliseconds = cacheTtlsMilliseconds; - } - - static isFiat(caipAssetId: CaipAssetType): boolean { - return caipAssetId.includes('swift:0/iso4217:'); } async getAccountAssetsByIDs( @@ -941,27 +905,6 @@ export class SnapAssetsAdapter { return this.#tokenApiClient.getTokensMetadata(assetTypes); } - /** - * Checks if the asset has changed compared to passed assets lookup. - * - * @param asset - The asset to check. - * @param assetsLookup - The lookup table to check against. - * @returns True if the asset has changed, false otherwise. - */ - static hasChanged(asset: AssetEntity, assetsLookup: AssetEntity[]): boolean { - const savedAsset = assetsLookup.find( - (item) => - item.keyringAccountId === asset.keyringAccountId && - item.assetType === asset.assetType, - ); - - if (!savedAsset) { - return true; - } - - return savedAsset.rawAmount !== asset.rawAmount; - } - /** * Persist the latest fetched assets and emit the corresponding keyring events. * @@ -1182,376 +1125,4 @@ export class SnapAssetsAdapter { return [...savedAssets, ...missingEssentialAssets]; } - - /** - * Extracts the ISO 4217 currency code (aka fiat ticker) from a fiat CAIP-19 asset type. - * - * @param caipAssetType - The CAIP-19 asset type. - * @returns The fiat ticker. - */ - #extractFiatTicker(caipAssetType: CaipAssetType): FiatTicker { - if (!SnapAssetsAdapter.isFiat(caipAssetType)) { - throw new Error('Passed caipAssetType is not a fiat asset'); - } - - const fiatTicker = - parseCaipAssetType(caipAssetType).assetReference.toLowerCase(); - - return fiatTicker as FiatTicker; - } - - /** - * Fetches fiat exchange rates and crypto prices for the given assets. - * This is shared logic between getMultipleTokenConversions and getMultipleTokensMarketData. - * - * @param allAssets - Array of all CAIP asset types (both fiat and crypto). - * @returns Promise resolving to fiat exchange rates and crypto prices. - */ - async #fetchPriceData(allAssets: CaipAssetType[]): Promise<{ - fiatExchangeRates: Record; - cryptoPrices: Record; - }> { - const cryptoAssets = allAssets.filter( - (asset) => !SnapAssetsAdapter.isFiat(asset), - ); - - const [fiatExchangeRates, cryptoPrices] = await Promise.all([ - this.#priceApiClient.getFiatExchangeRates(), - this.#priceApiClient.getMultipleSpotPrices(cryptoAssets, 'usd'), - ]); - - return { fiatExchangeRates, cryptoPrices }; - } - - /** - * Get the token conversions for a list of asset pairs. - * It caches the results for 1 hour. - * - * Beware: Inside we are using the Price API's `getFiatExchangeRates` method for fiat prices, - * `getMultipleSpotPrices` for crypto prices and then using USD as an intermediate currency - * to convert the prices to the correct currency. This is not entirely accurate but it's the - * best we can do with the current API. - * - * @param conversions - The asset pairs to get the conversions for. - * @returns The token conversions. - */ - async getMultipleTokenConversions( - conversions: { from: CaipAssetType; to: CaipAssetType }[], - ): Promise< - Record> - > { - if (conversions.length === 0) { - return {}; - } - - /** - * `from` and `to` can represent both fiat and crypto assets. For us to get their values - * the best approach is to use Price API's `getFiatExchangeRates` method for fiat prices, - * `getMultipleSpotPrices` for crypto prices and then using USD as an intermediate currency - * to convert the prices to the correct currency. - */ - const allAssets = conversions.flatMap((conversion) => [ - conversion.from, - conversion.to, - ]); - - const { fiatExchangeRates, cryptoPrices } = - await this.#fetchPriceData(allAssets); - - /** - * Now that we have the data, convert the `from`s to `to`s. - * - * We need to handle the following cases: - * 1. `from` and `to` are both fiat - * 2. `from` and `to` are both crypto - * 3. `from` is fiat and `to` is crypto - * 4. `from` is crypto and `to` is fiat - * - * We also need to keep in mind that although `cryptoPrices` are indexed - * by CAIP 19 IDs, the `fiatExchangeRates` are indexed by currency symbols. - * To convert fiat currency symbols to CAIP 19 IDs, we can use the - * `this.#fiatSymbolToCaip19Id` method. - */ - - const result: Record< - CaipAssetType, - Record - > = {}; - - conversions.forEach((conversion) => { - const { from, to } = conversion; - - result[from] ??= {}; - - let fromUsdRate: BigNumber; - let toUsdRate: BigNumber; - - if (SnapAssetsAdapter.isFiat(from)) { - /** - * Beware: - * We need to invert the fiat exchange rate because exchange rate != spot price - */ - const fiatExchangeRate = - fiatExchangeRates[this.#extractFiatTicker(from)]?.value; - - if (!fiatExchangeRate) { - result[from][to] = null; - return; - } - - fromUsdRate = new BigNumber(1).dividedBy(fiatExchangeRate); - } else { - fromUsdRate = new BigNumber(cryptoPrices[from]?.price ?? 0); - } - - if (SnapAssetsAdapter.isFiat(to)) { - /** - * Beware: - * We need to invert the fiat exchange rate because exchange rate != spot price - */ - const fiatExchangeRate = - fiatExchangeRates[this.#extractFiatTicker(to)]?.value; - - if (!fiatExchangeRate) { - result[from][to] = null; - return; - } - - toUsdRate = new BigNumber(1).dividedBy(fiatExchangeRate); - } else { - toUsdRate = new BigNumber(cryptoPrices[to]?.price ?? 0); - } - - if (fromUsdRate.isZero() || toUsdRate.isZero()) { - result[from][to] = null; - return; - } - - const rate = fromUsdRate.dividedBy(toUsdRate).toString(); - - const now = Date.now(); - - result[from][to] = { - rate, - conversionTime: now, - expirationTime: now + this.cacheTtlsMilliseconds.historicalPrices, - }; - }); - - return result; - } - - /** - * Computes the market data object in the target currency. - * - * @param spotPrice - The spot price of the asset in source currency. - * @param rate - The rate to convert the market data to from source currency to target currency. - * @returns The market data in the target currency. - */ - #computeMarketData( - spotPrice: SpotPrice, - rate: BigNumber, - ): FungibleAssetMarketData { - const marketDataInUsd = pick(spotPrice, [ - 'marketCap', - 'totalVolume', - 'circulatingSupply', - 'allTimeHigh', - 'allTimeLow', - 'pricePercentChange1h', - 'pricePercentChange1d', - 'pricePercentChange7d', - 'pricePercentChange14d', - 'pricePercentChange30d', - 'pricePercentChange200d', - 'pricePercentChange1y', - ]); - - const toCurrency = (value: number | null | undefined): string => { - return value === null || value === undefined - ? '' - : new BigNumber(value).dividedBy(rate).toString(); - }; - - const includeIfDefined = ( - key: string, - value: number | null | undefined, - ): Record => { - return value === null || value === undefined ? {} : { [key]: value }; - }; - - // Variations in percent don't need to be converted, they are independent of the currency - const pricePercentChange = { - ...includeIfDefined('PT1H', marketDataInUsd.pricePercentChange1h), - ...includeIfDefined('P1D', marketDataInUsd.pricePercentChange1d), - ...includeIfDefined('P7D', marketDataInUsd.pricePercentChange7d), - ...includeIfDefined('P14D', marketDataInUsd.pricePercentChange14d), - ...includeIfDefined('P30D', marketDataInUsd.pricePercentChange30d), - ...includeIfDefined('P200D', marketDataInUsd.pricePercentChange200d), - ...includeIfDefined('P1Y', marketDataInUsd.pricePercentChange1y), - }; - - const marketDataInToCurrency = { - fungible: true, - marketCap: toCurrency(marketDataInUsd.marketCap), - totalVolume: toCurrency(marketDataInUsd.totalVolume), - circulatingSupply: (marketDataInUsd.circulatingSupply ?? 0).toString(), // Circulating supply counts the number of tokens in circulation, so we don't convert - allTimeHigh: toCurrency(marketDataInUsd.allTimeHigh), - allTimeLow: toCurrency(marketDataInUsd.allTimeLow), - // Add pricePercentChange field only if it has values - ...(Object.keys(pricePercentChange).length > 0 - ? { pricePercentChange } - : {}), - } as FungibleAssetMarketData; - - return marketDataInToCurrency; - } - - async getMultipleTokensMarketData( - assets: { - asset: CaipAssetType; - unit: CaipAssetType; - }[], - ): Promise< - Record> - > { - if (assets.length === 0) { - return {}; - } - - /** - * `asset` and `unit` can represent both fiat and crypto assets. For us to get their values - * the best approach is to use Price API's `getFiatExchangeRates` method for fiat prices, - * `getMultipleSpotPrices` for crypto prices and then using USD as an intermediate currency - * to convert the prices to the correct currency. - */ - const allAssets = assets.flatMap((asset) => [asset.asset, asset.unit]); - - const { fiatExchangeRates, cryptoPrices } = - await this.#fetchPriceData(allAssets); - - const result: Record< - CaipAssetType, - Record - > = {}; - - assets.forEach((asset) => { - const { asset: assetType, unit } = asset; - - // Skip if we don't have price data for the asset - if (!cryptoPrices[assetType]) { - return; - } - - let unitUsdRate: BigNumber; - - if (SnapAssetsAdapter.isFiat(unit)) { - /** - * Beware: - * We need to invert the fiat exchange rate because exchange rate != spot price - */ - const fiatExchangeRate = - fiatExchangeRates[this.#extractFiatTicker(unit)]?.value; - - if (!fiatExchangeRate) { - return; - } - - unitUsdRate = new BigNumber(1).dividedBy(fiatExchangeRate); - } else { - unitUsdRate = new BigNumber(cryptoPrices[unit]?.price ?? 0); - } - - if (unitUsdRate.isZero()) { - return; - } - - // Initialize the nested structure for the asset if it doesn't exist - result[assetType] ??= {}; - - // Store the market data with the unit as the key - result[assetType][unit] = this.#computeMarketData( - cryptoPrices[assetType], - unitUsdRate, - ); - }); - - return result; - } - - /** - * Get historical prices for a token pair by calling the Price API. - * Similar to the Solana snap implementation. - * - * @param from - The asset to get historical prices for. - * @param to - The currency to convert prices to. - * @returns Historical price data with intervals. - */ - async getHistoricalPrice( - from: CaipAssetType, - to: CaipAssetType, - ): Promise<{ - intervals: HistoricalPriceIntervals; - updateTime: number; - expirationTime?: number; - }> { - assert(from, CaipAssetTypeStruct); - assert(to, CaipAssetTypeStruct); - - const toTicker = parseCaipAssetType(to).assetReference.toLowerCase(); - assert(toTicker, VsCurrencyParamStruct); - - const timePeriodsToFetch = ['1d', '7d', '1m', '3m', '1y', '1000y']; - - // For each time period, call the Price API to fetch the historical prices - const promises = timePeriodsToFetch.map(async (timePeriod) => - this.#priceApiClient - .getHistoricalPrices({ - assetType: from, - timePeriod, - vsCurrency: toTicker, - }) - // Wrap the response in an object with the time period and the response for easier reducing - .then((response) => ({ - timePeriod, - response, - })) - // Gracefully handle individual errors to avoid breaking the entire operation - .catch(async (error) => { - await this.#snapClient.trackError(error as Error); - this.#logger.warn( - `Error fetching historical prices for ${from} to ${to} with time period ${timePeriod}. Returning null object.`, - error, - ); - return { - timePeriod, - response: GET_HISTORICAL_PRICES_RESPONSE_NULL_OBJECT, - }; - }), - ); - - const wrappedHistoricalPrices = await Promise.all(promises); - - const intervals = wrappedHistoricalPrices.reduce( - (acc, { timePeriod, response }) => { - const iso8601Interval = `P${timePeriod.toUpperCase()}`; - acc[iso8601Interval] = response.prices.map((price) => [ - price[0], - price[1].toString(), - ]); - return acc; - }, - {}, - ); - - const now = Date.now(); - - const result = { - intervals, - updateTime: now, - expirationTime: now + this.cacheTtlsMilliseconds.historicalPrices, - }; - - return result; - } } diff --git a/packages/tron-wallet-snap/src/services/assets/types.ts b/packages/tron-wallet-snap/src/services/assets/types.ts index f8635428a..d03b20a4e 100644 --- a/packages/tron-wallet-snap/src/services/assets/types.ts +++ b/packages/tron-wallet-snap/src/services/assets/types.ts @@ -40,30 +40,6 @@ export const StakedCaipAssetTypeStruct = pattern( /^tron:(728126428|3448148188|2494104990)\/slip44:195-staked-for-(energy|bandwidth)$/u, ); -/** - * Validates a TRON ready-for-withdrawal CAIP-19 ID (e.g., "tron:728126428/slip44:195-ready-for-withdrawal") - */ -export const ReadyForWithdrawalCaipAssetTypeStruct = pattern( - CaipAssetTypeStruct, - /^tron:(728126428|3448148188|2494104990)\/slip44:195-ready-for-withdrawal$/u, -); - -/** - * Validates a TRON staking rewards CAIP-19 ID (e.g., "tron:728126428/slip44:195-staking-rewards") - */ -export const StakingRewardsCaipAssetTypeStruct = pattern( - CaipAssetTypeStruct, - /^tron:(728126428|3448148188|2494104990)\/slip44:195-staking-rewards$/u, -); - -/** - * Validates a TRON in-lock-period CAIP-19 ID (e.g., "tron:728126428/slip44:195-in-lock-period") - */ -export const InLockPeriodCaipAssetTypeStruct = pattern( - CaipAssetTypeStruct, - /^tron:(728126428|3448148188|2494104990)\/slip44:195-in-lock-period$/u, -); - /** * Validates a TRON native CAIP-19 ID for resources (e.g., "tron:728126428/energy" or "tron:728126428/bandwidth") */ diff --git a/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.test.ts b/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.test.ts index c495abcdb..388e00dbf 100644 --- a/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.test.ts +++ b/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.test.ts @@ -1,10 +1,7 @@ import type { CaipAssetType } from '@metamask/utils'; -import { KnownCaip19Id, Network, SNAP_OWNED_ASSETS } from '../../../constants'; -import { - getSnapOwnedAssetIdsForScope, - isSnapOwnedAsset, -} from './isSnapOwnedAsset'; +import { KnownCaip19Id, SNAP_OWNED_ASSETS } from '../../../constants'; +import { isSnapOwnedAsset } from './isSnapOwnedAsset'; describe('isSnapOwnedAsset', () => { it.each(SNAP_OWNED_ASSETS)( @@ -37,18 +34,3 @@ describe('isSnapOwnedAsset', () => { ).toBe(false); }); }); - -describe('getSnapOwnedAssetIdsForScope', () => { - it.each([Network.Mainnet, Network.Nile, Network.Shasta] as const)( - 'returns only snap-owned assets for %s', - (scope) => { - const assetIds = getSnapOwnedAssetIdsForScope(scope); - - expect(assetIds).toHaveLength(9); - expect(assetIds.every((assetId) => isSnapOwnedAsset(assetId))).toBe(true); - expect(assetIds.every((assetId) => assetId.startsWith(`${scope}/`))).toBe( - true, - ); - }, - ); -}); diff --git a/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.ts b/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.ts index 1bde3e6bf..cf54c0048 100644 --- a/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.ts +++ b/packages/tron-wallet-snap/src/services/assets/utils/isSnapOwnedAsset.ts @@ -1,37 +1,11 @@ import type { CaipAssetType } from '@metamask/utils'; -import { Networks, SNAP_OWNED_ASSETS } from '../../../constants'; -import type { Network } from '../../../constants'; +import { SNAP_OWNED_ASSETS } from '../../../constants'; const SNAP_OWNED_ASSET_IDS = new Set( SNAP_OWNED_ASSETS as CaipAssetType[], ); -/** - * Returns the full snap-owned asset ID set for a network scope. - * - * Matches the assets produced by `fetchAssetsAndBalancesForAccount` for that - * scope (staking positions and account resources, including zero balances). - * - * @param scope - The network to query. - * @returns CAIP-19 asset IDs exclusively managed by the Snap on that network. - */ -export function getSnapOwnedAssetIdsForScope(scope: Network): CaipAssetType[] { - const network = Networks[scope]; - - return [ - network.stakedForBandwidth.id, - network.stakedForEnergy.id, - network.readyForWithdrawal.id, - network.stakingRewards.id, - network.inLockPeriod.id, - network.bandwidth.id, - network.maximumBandwidth.id, - network.energy.id, - network.maximumEnergy.id, - ]; -} - /** * Returns whether an asset remains exclusively managed by the Snap. * diff --git a/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts b/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts index cc9ea5955..eadd1c435 100644 --- a/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts +++ b/packages/tron-wallet-snap/src/services/config/ConfigProvider.ts @@ -64,9 +64,7 @@ export type Config = { baseUrl: string; chunkSize: number; cacheTtlsMilliseconds: { - fiatExchangeRates: number; spotPrices: number; - historicalPrices: number; }; }; tokenApi: { @@ -177,9 +175,7 @@ export class ConfigProvider { : environment.PRICE_API_BASE_URL, chunkSize: 50, cacheTtlsMilliseconds: { - fiatExchangeRates: Duration.Minute, spotPrices: Duration.Minute, - historicalPrices: Duration.Minute, }, }, tokenApi: { diff --git a/packages/tron-wallet-snap/src/services/state/State.ts b/packages/tron-wallet-snap/src/services/state/State.ts index 287a4741a..15c941894 100644 --- a/packages/tron-wallet-snap/src/services/state/State.ts +++ b/packages/tron-wallet-snap/src/services/state/State.ts @@ -10,7 +10,6 @@ import type { MutexInterface } from 'async-mutex'; import { Mutex } from 'async-mutex'; import { unset } from 'lodash'; -import type { SpotPrices } from '../../clients/price-api/types'; import type { AssetEntity } from '../../entities/assets'; import type { TronKeyringAccount } from '../../entities/keyring-account'; import type { IStateManager } from './IStateManager'; @@ -20,7 +19,6 @@ export type AccountId = string; export type UnencryptedStateValue = { keyringAccounts: Record; assets: Record; - tokenPrices: SpotPrices; transactions: Record; mapInterfaceNameToId: Record; }; @@ -28,7 +26,6 @@ export type UnencryptedStateValue = { export const DEFAULT_UNENCRYPTED_STATE: UnencryptedStateValue = { keyringAccounts: {}, assets: {}, - tokenPrices: {}, transactions: {}, mapInterfaceNameToId: {}, };