From 64b52c8a32bb0743142d18f3a9d06ff101c7d6c9 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Wed, 2 Sep 2026 18:31:08 +0100 Subject: [PATCH 1/3] feat(solana-wallet-snap)!: remove asset handler entry points Remove the onAssetsLookup, onAssetsConversion, onAssetHistoricalPrice, and onAssetsMarketData entry points, along with their now-unused handler modules and the endowment:assets permission. Closes WPN-2013 --- packages/solana-wallet-snap/CHANGELOG.md | 4 + .../onAssetHistoricalPrice.ts | 29 -- .../onAssetsConversion/onAssetsConversion.ts | 17 - .../handlers/onAssetsLookup/onAssetsLookup.ts | 30 -- .../onAssetsMarketData.test.ts | 358 ------------------ .../onAssetsMarketData/onAssetsMarketData.ts | 14 - packages/solana-wallet-snap/src/index.ts | 38 -- 7 files changed, 4 insertions(+), 486 deletions(-) delete mode 100644 packages/solana-wallet-snap/src/core/handlers/onAssetHistoricalPrice/onAssetHistoricalPrice.ts delete mode 100644 packages/solana-wallet-snap/src/core/handlers/onAssetsConversion/onAssetsConversion.ts delete mode 100644 packages/solana-wallet-snap/src/core/handlers/onAssetsLookup/onAssetsLookup.ts delete mode 100644 packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.ts diff --git a/packages/solana-wallet-snap/CHANGELOG.md b/packages/solana-wallet-snap/CHANGELOG.md index 2858f3678..aee74261d 100644 --- a/packages/solana-wallet-snap/CHANGELOG.md +++ b/packages/solana-wallet-snap/CHANGELOG.md @@ -17,6 +17,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 `onAssetsLookup`, `onAssetsConversion`, `onAssetHistoricalPrice`, and `onAssetsMarketData` asset handler entry points, along with the now-unused handler modules and the `endowment:assets` permission ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) + ### Fixed - **BREAKING:** Preserve dapp-origin `signTransaction` and `signAndSendTransaction` payloads by signing the decoded transaction directly ([#156](https://github.com/MetaMask/internal-snaps/pull/156)) diff --git a/packages/solana-wallet-snap/src/core/handlers/onAssetHistoricalPrice/onAssetHistoricalPrice.ts b/packages/solana-wallet-snap/src/core/handlers/onAssetHistoricalPrice/onAssetHistoricalPrice.ts deleted file mode 100644 index 0bf2461a7..000000000 --- a/packages/solana-wallet-snap/src/core/handlers/onAssetHistoricalPrice/onAssetHistoricalPrice.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { OnAssetHistoricalPriceHandler } from '@metamask/snaps-sdk'; -import { assert } from '@metamask/superstruct'; -import { CaipAssetTypeStruct } from '@metamask/utils'; - -import { tokenPricesService } from '../../../snapContext'; -import logger from '../../utils/logger'; - -/** - * Implements the `onAssetHistoricalPrice` handler. - * - * @see https://github.com/MetaMask/SIPs/blob/main/SIPS/sip-29.md#get-assets-historical-price - * @param params - The parameters for the `onAssetHistoricalPrice` handler. - * @returns The historical price of the asset pair. - */ -export const onAssetHistoricalPrice: OnAssetHistoricalPriceHandler = async ( - params, -) => { - logger.log('[📈 onAssetHistoricalPrice]', params); - - const { from, to } = params; - assert(from, CaipAssetTypeStruct); - assert(to, CaipAssetTypeStruct); - - const historicalPrice = await tokenPricesService.getHistoricalPrice(from, to); - - return { - historicalPrice, - }; -}; diff --git a/packages/solana-wallet-snap/src/core/handlers/onAssetsConversion/onAssetsConversion.ts b/packages/solana-wallet-snap/src/core/handlers/onAssetsConversion/onAssetsConversion.ts deleted file mode 100644 index fef0f375c..000000000 --- a/packages/solana-wallet-snap/src/core/handlers/onAssetsConversion/onAssetsConversion.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { OnAssetsConversionHandler } from '@metamask/snaps-sdk'; - -import { tokenPricesService } from '../../../snapContext'; -import logger from '../../utils/logger'; - -export const onAssetsConversion: OnAssetsConversionHandler = async (params) => { - logger.log('[💱 onAssetsConversion]', params); - - const { conversions } = params; - - const conversionRates = - await tokenPricesService.getMultipleTokenConversions(conversions); - - return { - conversionRates, - }; -}; diff --git a/packages/solana-wallet-snap/src/core/handlers/onAssetsLookup/onAssetsLookup.ts b/packages/solana-wallet-snap/src/core/handlers/onAssetsLookup/onAssetsLookup.ts deleted file mode 100644 index 5d43b213d..000000000 --- a/packages/solana-wallet-snap/src/core/handlers/onAssetsLookup/onAssetsLookup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { CaipAssetType, FungibleAssetMetadata } from '@metamask/snaps-sdk'; -import type { OnAssetsLookupHandler } from '@metamask/snaps-sdk'; -import { parseCaipAssetType } from '@metamask/utils'; - -import { assetsService } from '../../../snapContext'; -import type { - NativeCaipAssetType, - TokenCaipAssetType, -} from '../../constants/solana'; -import logger from '../../utils/logger'; - -export const onAssetsLookup: OnAssetsLookupHandler = async (params) => { - logger.log('[🔍 onAssetsLookup]', params); - - const { assets } = params; - - /** - * TODO: Remove me when we have the new version of Snaps SDK - */ - const fungibleAssets = assets.filter((asset) => { - const { assetNamespace } = parseCaipAssetType(asset); - return assetNamespace === 'token' || assetNamespace === 'slip44'; - }) as (TokenCaipAssetType | NativeCaipAssetType)[]; - - const metadata = (await assetsService.getAssetsMetadata( - fungibleAssets, - )) as Record; - - return { assets: metadata }; -}; diff --git a/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.test.ts b/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.test.ts deleted file mode 100644 index e3ee6c1ea..000000000 --- a/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.test.ts +++ /dev/null @@ -1,358 +0,0 @@ -import type { FungibleAssetMarketData } from '@metamask/snaps-sdk'; -import type { CaipAssetType } from '@metamask/utils'; - -import { assetsService } from '../../../snapContext'; -import logger from '../../utils/logger'; -import { onAssetsMarketData } from './onAssetsMarketData'; - -jest.mock('../../../snapContext', () => ({ - assetsService: { - fetchAssetsMarketData: jest.fn(), - }, -})); - -jest.mock('../../utils/logger', () => ({ - log: jest.fn(), - error: jest.fn(), -})); - -describe('onAssetsMarketData', () => { - const mockAssetsService = assetsService as jest.Mocked; - - const BTC = - 'bip122:000000000019d6689c085ae165831e93/slip44:0' as CaipAssetType; - const ETH = 'eip155:1/slip44:60' as CaipAssetType; - const SOL = - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' as CaipAssetType; - const USD = 'swift:0/iso4217:USD' as CaipAssetType; - const EUR = 'swift:0/iso4217:EUR' as CaipAssetType; - - const PT1H = 'PT1H'; - const P1D = 'P1D'; - const P7D = 'P7D'; - const P30D = 'P30D'; - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('successful scenarios', () => { - it('should return market data for crypto assets in USD', async () => { - const params = { - assets: [ - { asset: BTC, unit: USD }, - { asset: ETH, unit: USD }, - { asset: SOL, unit: USD }, - ], - }; - - const mockMarketData: Record = { - [BTC]: { - fungible: true, - marketCap: '1000000000000', - totalVolume: '50000000000', - circulatingSupply: '19500000', - allTimeHigh: '120000', - allTimeLow: '67.81', - pricePercentChange: { - [PT1H]: 0.5, - [P1D]: 2.1, - [P7D]: -1.2, - [P30D]: 15.3, - }, - }, - [ETH]: { - fungible: true, - marketCap: '400000000000', - totalVolume: '20000000000', - circulatingSupply: '120000000', - allTimeHigh: '5000', - allTimeLow: '0.43', - pricePercentChange: { - [PT1H]: 1.2, - [P1D]: 3.5, - [P7D]: 5.1, - }, - }, - [SOL]: { - fungible: true, - marketCap: '80000000000', - totalVolume: '3000000000', - circulatingSupply: '400000000', - allTimeHigh: '260', - allTimeLow: '0.5', - pricePercentChange: { - [PT1H]: -0.8, - [P1D]: 1.5, - [P7D]: -2.3, - }, - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should return market data for crypto assets in different fiat currencies', async () => { - const params = { - assets: [ - { asset: BTC, unit: EUR }, - { asset: ETH, unit: USD }, - ], - }; - - const mockMarketData: Record = { - [BTC]: { - fungible: true, - marketCap: '850000000000', - totalVolume: '42500000000', - circulatingSupply: '19500000', - allTimeHigh: '102000', - allTimeLow: '57.64', - pricePercentChange: { - [PT1H]: 0.3, - [P1D]: 1.8, - }, - }, - [ETH]: { - fungible: true, - marketCap: '400000000000', - totalVolume: '20000000000', - circulatingSupply: '120000000', - allTimeHigh: '5000', - allTimeLow: '0.43', - pricePercentChange: { - [PT1H]: 1.2, - [P1D]: 3.5, - }, - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should return market data with minimal fields when some data is missing', async () => { - const params = { - assets: [{ asset: BTC, unit: USD }], - }; - - const mockMarketData: Record = { - [BTC]: { - fungible: true, - marketCap: '1000000000000', - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should return empty market data when no assets are provided', async () => { - const params = { - assets: [], - }; - - const mockMarketData: Record = {}; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should return market data with only price percent changes when other fields are null', async () => { - const params = { - assets: [{ asset: BTC, unit: USD }], - }; - - const mockMarketData: Record = { - [BTC]: { - fungible: true, - pricePercentChange: { - [PT1H]: 0.5, - [P1D]: 2.1, - [P7D]: -1.2, - }, - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - }); - - describe('edge cases', () => { - it('should handle assets with special characters in asset types', async () => { - const specialAsset = - 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1/slip44:501' as CaipAssetType; - const params = { - assets: [{ asset: specialAsset, unit: USD }], - }; - - const mockMarketData: Record = { - [specialAsset]: { - fungible: true, - marketCap: '50000000', - totalVolume: '2000000', - circulatingSupply: '1000000', - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should handle very large numbers in market data', async () => { - const params = { - assets: [{ asset: BTC, unit: USD }], - }; - - const mockMarketData: Record = { - [BTC]: { - fungible: true, - marketCap: '999999999999999999999999999999', - totalVolume: '123456789012345678901234567890', - circulatingSupply: '21000000', - allTimeHigh: '999999999999999999999999999999', - allTimeLow: '0.000000000000000001', - pricePercentChange: { - [PT1H]: 999.99, - [P1D]: -999.99, - }, - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - - it('should handle zero values in market data', async () => { - const params = { - assets: [{ asset: ETH, unit: USD }], - }; - - const mockMarketData: Record = { - [ETH]: { - fungible: true, - marketCap: '0', - totalVolume: '0', - circulatingSupply: '0', - allTimeHigh: '0', - allTimeLow: '0', - pricePercentChange: { - [PT1H]: 0, - [P1D]: 0, - [P7D]: 0, - }, - }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - const result = await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - expect(mockAssetsService.fetchAssetsMarketData).toHaveBeenCalledWith( - params.assets, - ); - expect(result).toStrictEqual({ marketData: mockMarketData }); - }); - }); - - describe('logging behavior', () => { - it('should log the input parameters correctly', async () => { - const params = { - assets: [ - { asset: BTC, unit: USD }, - { asset: ETH, unit: EUR }, - ], - }; - - const mockMarketData: Record = { - [BTC]: { fungible: true }, - [ETH]: { fungible: true }, - }; - - mockAssetsService.fetchAssetsMarketData.mockResolvedValue(mockMarketData); - - await onAssetsMarketData(params); - - expect(logger.log).toHaveBeenCalledWith( - '[💰 onAssetsMarketData]', - params, - ); - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.ts b/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.ts deleted file mode 100644 index 021437936..000000000 --- a/packages/solana-wallet-snap/src/core/handlers/onAssetsMarketData/onAssetsMarketData.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { OnAssetsMarketDataHandler } from '@metamask/snaps-sdk'; - -import { assetsService } from '../../../snapContext'; -import logger from '../../utils/logger'; - -export const onAssetsMarketData: OnAssetsMarketDataHandler = async (params) => { - logger.log('[💰 onAssetsMarketData]', params); - - const { assets } = params; - - const marketData = await assetsService.fetchAssetsMarketData(assets); - - return { marketData }; -}; diff --git a/packages/solana-wallet-snap/src/index.ts b/packages/solana-wallet-snap/src/index.ts index 6060f0b74..41c3c68ac 100644 --- a/packages/solana-wallet-snap/src/index.ts +++ b/packages/solana-wallet-snap/src/index.ts @@ -5,10 +5,6 @@ import { MethodNotFoundError } from '@metamask/snaps-sdk'; import type { Json, OnActiveHandler, - OnAssetHistoricalPriceHandler, - OnAssetsConversionHandler, - OnAssetsLookupHandler, - OnAssetsMarketDataHandler, OnClientRequestHandler, OnCronjobHandler, OnInactiveHandler, @@ -25,10 +21,6 @@ import type { OnRpcRequestHandler } from '@metamask/snaps-sdk'; import { assert, enums } from '@metamask/superstruct'; import BigNumber from 'bignumber.js'; -import { onAssetHistoricalPrice as onAssetHistoricalPriceHandler } from './core/handlers/onAssetHistoricalPrice/onAssetHistoricalPrice'; -import { onAssetsConversion as onAssetsConversionHandler } from './core/handlers/onAssetsConversion/onAssetsConversion'; -import { onAssetsLookup as onAssetsLookupHandler } from './core/handlers/onAssetsLookup/onAssetsLookup'; -import { onAssetsMarketData as onAssetsMarketDataHandler } from './core/handlers/onAssetsMarketData/onAssetsMarketData'; import { handlers as onCronjobHandlers } from './core/handlers/onCronjob'; import { ScheduleBackgroundEventMethod } from './core/handlers/onCronjob/backgroundEvents/ScheduleBackgroundEventMethod'; import { CronjobMethod } from './core/handlers/onCronjob/cronjobs/CronjobMethod'; @@ -207,20 +199,6 @@ export const onCronjob: OnCronjobHandler = async ({ request }) => { return result ?? null; }; -export const onAssetsLookup: OnAssetsLookupHandler = async (params) => { - const result = await withCatchAndThrowSnapError(async () => - onAssetsLookupHandler(params), - ); - return result ?? null; -}; - -export const onAssetsConversion: OnAssetsConversionHandler = async (params) => { - const result = await withCatchAndThrowSnapError(async () => - onAssetsConversionHandler(params), - ); - return result ?? null; -}; - export const onProtocolRequest: OnProtocolRequestHandler = async (params) => { const result = await withCatchAndThrowSnapError(async () => onProtocolRequestHandler(params), @@ -228,15 +206,6 @@ export const onProtocolRequest: OnProtocolRequestHandler = async (params) => { return result ?? null; }; -export const onAssetHistoricalPrice: OnAssetHistoricalPriceHandler = async ( - params, -) => { - const result = await withCatchAndThrowSnapError(async () => - onAssetHistoricalPriceHandler(params), - ); - return result ?? null; -}; - export const onClientRequest: OnClientRequestHandler = async ({ request }) => { const result = await withCatchAndThrowSnapError(async () => clientRequestHandler.handle(request), @@ -286,10 +255,3 @@ export const onNameLookup: OnNameLookupHandler = async (request) => { ); return result ?? null; }; - -export const onAssetsMarketData: OnAssetsMarketDataHandler = async (params) => { - const result = await withCatchAndThrowSnapError(async () => - onAssetsMarketDataHandler(params), - ); - return result ?? null; -}; From 302ada576650644e163fde440cd4039e304e7421 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Thu, 3 Sep 2026 16:42:30 +0100 Subject: [PATCH 2/3] refactor(solana-wallet-snap): remove dead price and market data code Remove the code left orphaned by the asset handler removal: - AssetsService.fetchAssetsMarketData and SnapAssetsAdapter.fetchAssetsMarketData - TokenPricesService (getMultipleTokensMarketData, getMultipleTokenConversions, getHistoricalPrice) and its types/tests - PriceApiClient.getHistoricalPrices and historical price types/mocks - tokenPricesService wiring in SnapAssetsAdapter and snapContext --- packages/solana-wallet-snap/CHANGELOG.md | 1 + .../clients/price-api/PriceApiClient.test.ts | 56 -- .../core/clients/price-api/PriceApiClient.ts | 80 +-- .../price-api/mocks/historical-prices.ts | 17 - .../src/core/clients/price-api/types.ts | 32 -- .../services/assets/AssetsService.test.ts | 34 -- .../src/core/services/assets/AssetsService.ts | 12 - .../assets/adapters/SnapAssetsAdapter.ts | 26 +- .../services/token-prices/TokenPrices.test.ts | 523 ------------------ .../core/services/token-prices/TokenPrices.ts | 414 -------------- .../src/core/services/token-prices/types.ts | 4 - .../src/core/test/mocks/market-data.ts | 243 -------- .../solana-wallet-snap/src/snapContext.ts | 10 - 13 files changed, 4 insertions(+), 1448 deletions(-) delete mode 100644 packages/solana-wallet-snap/src/core/clients/price-api/mocks/historical-prices.ts delete mode 100644 packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.ts delete mode 100644 packages/solana-wallet-snap/src/core/services/token-prices/types.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/market-data.ts diff --git a/packages/solana-wallet-snap/CHANGELOG.md b/packages/solana-wallet-snap/CHANGELOG.md index aee74261d..0fc7356bd 100644 --- a/packages/solana-wallet-snap/CHANGELOG.md +++ b/packages/solana-wallet-snap/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - **BREAKING** Remove the `onAssetsLookup`, `onAssetsConversion`, `onAssetHistoricalPrice`, and `onAssetsMarketData` asset handler entry points, along with the now-unused handler modules and the `endowment:assets` permission ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) +- **BREAKING** Remove `AssetsService.fetchAssetsMarketData` and `SnapAssetsAdapter.fetchAssetsMarketData`, the `TokenPricesService` class, and `PriceApiClient.getHistoricalPrices`, all of which were only used by the removed asset handlers ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) ### Fixed diff --git a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts index cba741ba4..6a34946a0 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts @@ -9,7 +9,6 @@ import { KnownCaip19Id } from '../../constants/solana'; import { mockLogger } from '../../services/__mocks__/logger'; import type { ConfigProvider } from '../../services/config'; import { MOCK_EXCHANGE_RATES } from '../../test/mocks/price-api/exchange-rates'; -import { MOCK_HISTORICAL_PRICES } from './mocks/historical-prices'; import { MOCK_SPOT_PRICES } from './mocks/spot-prices'; import { PriceApiClient } from './PriceApiClient'; import type { SpotPrices, VsCurrencyParam } from './types'; @@ -359,59 +358,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.SolMainnet, - timePeriod: '5d', - from: 123, - to: 456, - vsCurrency: 'usd', - }); - - expect(mockFetch).toHaveBeenCalledWith( - 'https://some-mock-url.com/v3/historical-prices/solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501?timePeriod=5d&from=123&to=456&vsCurrency=usd', - ); - expect(cacheSetSpy).toHaveBeenCalledWith( - 'PriceApiClient:getHistoricalPrices:{"assetType":"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501","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.SolMainnet, - 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/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts index a1b506eaf..5274c6c52 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts @@ -11,20 +11,8 @@ import type { ICache } from '../../caching/ICache'; import { useCache } from '../../caching/useCache'; import type { ConfigProvider } from '../../services/config'; import logger from '../../utils/logger'; -import type { - ExchangeRate, - FiatTicker, - GetHistoricalPricesParams, - GetHistoricalPricesResponse, - SpotPrices, - VsCurrencyParam, -} from './types'; -import { - GetHistoricalPricesParamsStruct, - GetHistoricalPricesResponseStruct, - SpotPricesStruct, - VsCurrencyParamStruct, -} from './types'; +import type { ExchangeRate, FiatTicker, SpotPrices, VsCurrencyParam } from './types'; +import { SpotPricesStruct, VsCurrencyParamStruct } from './types'; export class PriceApiClient { readonly #fetch: typeof globalThis.fetch; @@ -265,68 +253,4 @@ export class PriceApiClient { ): Promise { return this.#getMultipleSpotPrices_CACHE(tokenCaip19Types, 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 { - assert(params, GetHistoricalPricesParamsStruct); - - const url = buildUrl({ - baseUrl: this.#baseUrl, - path: '/v3/historical-prices/{assetType}', - pathParams: { - assetType: params.assetType, - }, - encodePathParams: false, - queryParams: { - ...(params.timePeriod && { timePeriod: params.timePeriod }), - ...(params.from && { from: params.from.toString() }), - ...(params.to && { to: params.to.toString() }), - ...(params.vsCurrency && { vsCurrency: params.vsCurrency }), - }, - }); - - 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 { - return useCache( - this.#getHistoricalPrices_INTERNAL.bind(this), - this.#cache, - { - functionName: 'PriceApiClient:getHistoricalPrices', - ttlMilliseconds: this.cacheTtlsMilliseconds.historicalPrices, - }, - )(params); - } } diff --git a/packages/solana-wallet-snap/src/core/clients/price-api/mocks/historical-prices.ts b/packages/solana-wallet-snap/src/core/clients/price-api/mocks/historical-prices.ts deleted file mode 100644 index c49401ef5..000000000 --- a/packages/solana-wallet-snap/src/core/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/solana-wallet-snap/src/core/clients/price-api/types.ts b/packages/solana-wallet-snap/src/core/clients/price-api/types.ts index 40ce9dc8c..b3029d658 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/types.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/types.ts @@ -1,6 +1,5 @@ import type { Infer } from '@metamask/superstruct'; import { - array, boolean, enums, min, @@ -8,10 +7,8 @@ import { number, object, optional, - pattern, record, string, - tuple, union, } from '@metamask/superstruct'; import { CaipAssetTypeStruct } from '@metamask/utils'; @@ -197,32 +194,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/solana-wallet-snap/src/core/services/assets/AssetsService.test.ts b/packages/solana-wallet-snap/src/core/services/assets/AssetsService.test.ts index 5cf6ccf95..fa21c8a2e 100644 --- a/packages/solana-wallet-snap/src/core/services/assets/AssetsService.test.ts +++ b/packages/solana-wallet-snap/src/core/services/assets/AssetsService.test.ts @@ -23,7 +23,6 @@ import { MOCK_SOLANA_RPC_GET_TOKEN_ACCOUNTS_BY_OWNER_RESPONSE } from '../__mocks import type { AccountsService } from '../accounts/AccountsService'; import type { ConfigProvider } from '../config'; import type { SolanaConnection } from '../connection'; -import type { TokenPricesService } from '../token-prices/TokenPrices'; import { SnapAssetsAdapter } from './adapters/SnapAssetsAdapter'; import type { AssetsRepository } from './AssetsRepository'; import { AssetsService } from './AssetsService'; @@ -40,7 +39,6 @@ describe('AssetsService', () => { let mockAssetsRepository: AssetsRepository; let mockAccountsService: AccountsService; let mockTokenApiClient: TokenApiClient; - let mockTokenPricesService: TokenPricesService; let mockNftApiClient: NftApiClient; let mockCache: ICache; @@ -58,14 +56,6 @@ describe('AssetsService', () => { .mockResolvedValue(SOLANA_MOCK_TOKEN_METADATA), } as unknown as TokenApiClient; - mockTokenPricesService = { - getMultipleTokenConversions: jest.fn().mockResolvedValue({}), - getMultipleTokensMarketData: jest.fn().mockResolvedValue({}), - getHistoricalPrice: jest - .fn() - .mockResolvedValue({ intervals: {}, updateTime: 0, expirationTime: 0 }), - } as unknown as TokenPricesService; - mockCache = new InMemoryCache(mockLogger); mockNftApiClient = { @@ -96,7 +86,6 @@ describe('AssetsService', () => { assetsRepository: mockAssetsRepository, accountsService: mockAccountsService, tokenApiClient: mockTokenApiClient, - tokenPricesService: mockTokenPricesService, cache: mockCache, nftApiClient: mockNftApiClient, }); @@ -195,29 +184,6 @@ describe('AssetsService', () => { }); }); - describe('fetchAssetsMarketData', () => { - it('delegates to the token prices service', async () => { - const assets = [ - { - asset: MOCK_ASSET_ENTITY_0.assetType, - unit: MOCK_ASSET_ENTITY_0.assetType, - }, - ]; - const expected = { [MOCK_ASSET_ENTITY_0.assetType]: {} }; - - jest - .spyOn(mockTokenPricesService, 'getMultipleTokensMarketData') - .mockResolvedValueOnce(expected as never); - - const result = await assetsService.fetchAssetsMarketData(assets); - - expect( - mockTokenPricesService.getMultipleTokensMarketData, - ).toHaveBeenCalledWith(assets); - expect(result).toStrictEqual(expected); - }); - }); - describe('save', () => { it('saves an asset', async () => { const spy = jest diff --git a/packages/solana-wallet-snap/src/core/services/assets/AssetsService.ts b/packages/solana-wallet-snap/src/core/services/assets/AssetsService.ts index 5a1faabf3..1ace9e37b 100644 --- a/packages/solana-wallet-snap/src/core/services/assets/AssetsService.ts +++ b/packages/solana-wallet-snap/src/core/services/assets/AssetsService.ts @@ -1,5 +1,4 @@ /* eslint-disable jsdoc/require-returns */ -import type { FungibleAssetMarketData } from '@metamask/snaps-sdk'; import type { CaipAssetType, CaipChainId } from '@metamask/utils'; import type { AssetEntity, SolanaKeyringAccount } from '../../../entities'; @@ -34,17 +33,6 @@ export class AssetsService { return this.#snapAdapter.fetch(account); } - async fetchAssetsMarketData( - assets: { - asset: CaipAssetType; - unit: CaipAssetType; - }[], - ): Promise< - Record> - > { - return this.#snapAdapter.fetchAssetsMarketData(assets); - } - async save(asset: AssetEntity): Promise { await this.saveMany([asset]); } diff --git a/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts b/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts index c84d13e3e..a1672d6e1 100644 --- a/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts +++ b/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.ts @@ -7,10 +7,7 @@ import type { } from '@metamask/keyring-api'; import { emitSnapKeyringEvent } from '@metamask/keyring-snap-sdk'; import type { Logger, Serializable } from '@metamask/snap-networks-utils'; -import type { - FungibleAssetMarketData, - FungibleAssetMetadata, -} from '@metamask/snaps-sdk'; +import type { FungibleAssetMetadata } from '@metamask/snaps-sdk'; import type { CaipAssetType, CaipChainId } from '@metamask/utils'; import { Duration, parseCaipAssetType } from '@metamask/utils'; import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; @@ -46,7 +43,6 @@ import { tokenAddressToCaip19 } from '../../../utils/tokenAddressToCaip19'; import type { AccountsService } from '../../accounts/AccountsService'; import type { ConfigProvider } from '../../config'; import type { SolanaConnection } from '../../connection'; -import type { TokenPricesService } from '../../token-prices/TokenPrices'; import type { AssetsRepository } from '../AssetsRepository'; import type { AssetMetadata, NonFungibleAssetMetadata } from '../types'; @@ -73,8 +69,6 @@ export class SnapAssetsAdapter { readonly #tokenApiClient: TokenApiClient; - readonly #tokenPricesService: TokenPricesService; - readonly #cache: ICache; readonly #nftApiClient: NftApiClient; @@ -90,7 +84,6 @@ export class SnapAssetsAdapter { assetsRepository, accountsService, tokenApiClient, - tokenPricesService, cache, nftApiClient, }: { @@ -100,7 +93,6 @@ export class SnapAssetsAdapter { assetsRepository: AssetsRepository; accountsService: AccountsService; tokenApiClient: TokenApiClient; - tokenPricesService: TokenPricesService; cache: ICache; nftApiClient: NftApiClient; }) { @@ -110,7 +102,6 @@ export class SnapAssetsAdapter { this.#assetsRepository = assetsRepository; this.#accountsService = accountsService; this.#tokenApiClient = tokenApiClient; - this.#tokenPricesService = tokenPricesService; this.#cache = cache; this.#nftApiClient = nftApiClient; } @@ -430,21 +421,6 @@ export class SnapAssetsAdapter { return results; } - async fetchAssetsMarketData( - assets: { - asset: CaipAssetType; - unit: CaipAssetType; - }[], - ): Promise< - Record> - > { - this.#logger.info('Fetching market data for assets', assets); - - const marketData = - await this.#tokenPricesService.getMultipleTokensMarketData(assets); - return marketData; - } - async #fetchNftAssets( account: SolanaKeyringAccount, assetIds: NftCaipAssetType[], diff --git a/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.test.ts b/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.test.ts deleted file mode 100644 index 2ae91e22d..000000000 --- a/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.test.ts +++ /dev/null @@ -1,523 +0,0 @@ -import { Duration } from '@metamask/utils'; - -import { MOCK_HISTORICAL_PRICES } from '../../clients/price-api/mocks/historical-prices'; -import { MOCK_SPOT_PRICES } from '../../clients/price-api/mocks/spot-prices'; -import type { PriceApiClient } from '../../clients/price-api/PriceApiClient'; -import type { SpotPrice } from '../../clients/price-api/types'; -import { MOCK_EXCHANGE_RATES } from '../../test/mocks/price-api/exchange-rates'; -import { trackError } from '../../utils/errors'; -import { mockLogger } from '../__mocks__/logger'; -import { ConfigProvider } from '../config'; -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import { TokenPricesService } from './TokenPrices'; - -jest.mock('../../utils/errors', () => ({ - trackError: jest.fn().mockResolvedValue('tracked-error-id'), -})); - -describe('TokenPricesService', () => { - /* Crypto */ - const BTC = 'bip122:000000000019d6689c085ae165831e93/slip44:0'; - const ETH = 'eip155:1/slip44:60'; - const SOL = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'; - const USDC = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; - - /* Fiat */ - const EUR = 'swift:0/iso4217:EUR'; - const USD = 'swift:0/iso4217:USD'; - const BZR = 'swift:0/iso4217:BRL'; - - const UNKNOWN_CRYPTO_1 = 'unknown:1/slip44:1'; - const UNKNOWN_CRYPTO_2 = 'unknown:2/slip44:2'; - const UNKNOWN_FIAT_1 = 'swift:0/iso4217:AAA'; - const UNKNOWN_FIAT_2 = 'swift:0/iso4217:ZZZ'; - - let tokenPricesService: TokenPricesService; - let mockPriceApiClient: PriceApiClient; - let mockConfigProvider: ConfigProvider; - - beforeEach(() => { - mockPriceApiClient = { - getFiatExchangeRates: jest.fn().mockResolvedValue(MOCK_EXCHANGE_RATES), - getMultipleSpotPrices: jest.fn().mockResolvedValue(MOCK_SPOT_PRICES), - getHistoricalPrices: jest.fn().mockResolvedValue(MOCK_HISTORICAL_PRICES), - cacheTtlsMilliseconds: { - historicalPrices: Duration.Hour, - spotPrices: Duration.Hour, - }, - } as unknown as PriceApiClient; - - mockConfigProvider = new ConfigProvider(); - - tokenPricesService = new TokenPricesService({ - priceApiClient: mockPriceApiClient, - configProvider: mockConfigProvider, - logger: mockLogger, - }); - }); - - describe('getMultipleTokenConversions', () => { - it('returns empty object when no conversions provided', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([]); - expect(result).toStrictEqual({}); - }); - - describe('when includeMarketData is false', () => { - it('handles fiat to fiat conversions', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([ - /* Same currency */ - { from: USD, to: USD }, - { from: EUR, to: EUR }, - /* Different currency */ - { from: EUR, to: USD }, - { from: USD, to: BZR }, - { from: EUR, to: BZR }, - ]); - - expect(result).toStrictEqual( - expect.objectContaining({ - [USD]: expect.objectContaining({ - [USD]: { - rate: '1', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [BZR]: { - rate: '5.44630000241062899996', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - [EUR]: expect.objectContaining({ - [EUR]: { - rate: '1', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [USD]: { - rate: '1.17696630204744878672', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [BZR]: { - rate: '6.41011157367824942681', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - }), - ); - }); - - it('handles crypto to crypto conversions', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([ - /* Same currency */ - { from: BTC, to: BTC }, - { from: ETH, to: ETH }, - /* Different currency */ - { from: BTC, to: ETH }, - { from: ETH, to: SOL }, - { from: SOL, to: USDC }, - ]); - - expect(result).toStrictEqual( - expect.objectContaining({ - [BTC]: expect.objectContaining({ - [BTC]: { - rate: '1', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [ETH]: { - rate: '44.96458169857359389595', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - [ETH]: expect.objectContaining({ - [ETH]: { - rate: '1', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [SOL]: { - rate: '14.69103829451243642206', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - [SOL]: expect.objectContaining({ - [USDC]: { - rate: '126.65075990455942506931', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - }), - ); - }); - - it('handles crypto to fiat conversions', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([ - { from: BTC, to: USD }, - { from: ETH, to: USD }, - { from: SOL, to: USD }, - ]); - - expect(result).toStrictEqual( - expect.objectContaining({ - [BTC]: expect.objectContaining({ - [USD]: { - rate: '77556.84849999227', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - [ETH]: expect.objectContaining({ - [USD]: { - rate: '1724.8431002851428', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - [SOL]: expect.objectContaining({ - [USD]: { - rate: '117.40784182214172', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - }), - ); - }); - - it('handles fiat to crypto conversions', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([ - { from: USD, to: BTC }, - { from: USD, to: ETH }, - { from: USD, to: SOL }, - ]); - - expect(result).toStrictEqual( - expect.objectContaining({ - [USD]: expect.objectContaining({ - [BTC]: { - rate: '0.00001289376785339724', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [ETH]: { - rate: '0.00057976287804652191', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - [SOL]: { - rate: '0.00851731864311819686', - conversionTime: expect.any(Number), - expirationTime: expect.any(Number), - }, - }), - }), - ); - }); - - it('handles missing data correctly', async () => { - const result = await tokenPricesService.getMultipleTokenConversions([ - { from: UNKNOWN_CRYPTO_1, to: UNKNOWN_CRYPTO_2 }, - { from: UNKNOWN_CRYPTO_1, to: UNKNOWN_FIAT_1 }, - { from: UNKNOWN_FIAT_1, to: UNKNOWN_CRYPTO_2 }, - { from: UNKNOWN_FIAT_1, to: UNKNOWN_FIAT_2 }, - ]); - - expect(result).toStrictEqual({ - [UNKNOWN_CRYPTO_1]: { - [UNKNOWN_CRYPTO_2]: null, - [UNKNOWN_FIAT_1]: null, - }, - [UNKNOWN_FIAT_1]: { - [UNKNOWN_CRYPTO_2]: null, - [UNKNOWN_FIAT_2]: null, - }, - }); - }); - }); - }); - - describe('getMultipleTokensMarketData', () => { - it('returns empty object when no assets provided', async () => { - const result = await tokenPricesService.getMultipleTokensMarketData([]); - expect(result).toStrictEqual({}); - }); - - it('returns market data in the correct nested structure with asset-to-unit conversions and correct values', async () => { - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: BTC, unit: USD }, - { asset: ETH, unit: USD }, - { asset: SOL, unit: USD }, - { asset: SOL, unit: BTC }, - ]); - - // BTC/USD - actual values from consistent mocks - expect(result[BTC]![USD]).toStrictEqual({ - fungible: true, - marketCap: '1540421085883.0198', - totalVolume: '23748436299.895576', - circulatingSupply: '19844921', - allTimeHigh: '100847.44951017378', - allTimeLow: '62.86163248290115', - pricePercentChange: { - PT1H: -0.4456714429821922, - P1D: 1.3725526422881404, - P7D: -4.2914380354332256, - P14D: 1.3530761284206316, - P30D: -2.6647248645353425, - P200D: 44.69565022141291, - P1Y: 20.367003699380124, - }, - }); - - // ETH/USD - actual values from consistent mocks - expect(result[ETH]![USD]).toStrictEqual({ - fungible: true, - marketCap: '208326525244.77222', - totalVolume: '14672129201.423573', - circulatingSupply: '120659504.7581715', - allTimeHigh: '4522.273813243435', - allTimeLow: '0.4013827867691204', - pricePercentChange: { - PT1H: -0.16193070976498064, - P1D: 1.9964598342126199, - P7D: -10.123102834312476, - P14D: -1.7452971064771636, - P30D: -16.78602306244949, - P200D: -21.026646670919543, - P1Y: -47.45246230239663, - }, - }); - - // SOL/USD - actual values from consistent mocks - expect(result[SOL]![USD]).toStrictEqual({ - fungible: true, - marketCap: '60217502031.67665', - totalVolume: '3389485617.517553', - circulatingSupply: '512506275.4700137', - allTimeHigh: '271.90599356377726', - allTimeLow: '0.46425554356391946', - pricePercentChange: { - PT1H: -0.7015657267954617, - P1D: 1.6270441732346845, - P7D: -10.985589910714582, - P14D: 2.557473792001135, - P30D: -11.519171371325216, - P200D: -4.453777067234332, - P1Y: -35.331458644625535, - }, - }); - - // SOL/BTC - actual converted values from consistent mocks - expect(result[SOL]![BTC]).toStrictEqual({ - fungible: true, - marketCap: '776430.49190791515732749827', - totalVolume: '43703.24069470010538206139', - circulatingSupply: '512506275.4700137', - allTimeHigh: '0.00350589275895866708', - allTimeLow: '0.00000598600320336592', - pricePercentChange: { - PT1H: -0.7015657267954617, - P1D: 1.6270441732346845, - P7D: -10.985589910714582, - P14D: 2.557473792001135, - P30D: -11.519171371325216, - P200D: -4.453777067234332, - P1Y: -35.331458644625535, - }, - }); - }); - - it('only includes price percent change if Price API returns it', async () => { - jest - .spyOn(mockPriceApiClient, 'getMultipleSpotPrices') - .mockResolvedValue({ - [BTC]: { - ...MOCK_SPOT_PRICES[BTC], - pricePercentChange1h: -0.4456714429821922, - pricePercentChange1d: null, - pricePercentChange7d: null, - pricePercentChange14d: null, - pricePercentChange30d: null, - pricePercentChange200d: null, - pricePercentChange1y: null, - } as SpotPrice, - }); - - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: BTC, unit: USD }, - ]); - - expect(result[BTC]?.[USD]?.pricePercentChange).toStrictEqual({ - PT1H: -0.4456714429821922, - }); - }); - - it('does not include price percent change field if Price API does not return any values', async () => { - jest - .spyOn(mockPriceApiClient, 'getMultipleSpotPrices') - .mockResolvedValue({ - [BTC]: { - ...MOCK_SPOT_PRICES[BTC], - pricePercentChange1h: null, - pricePercentChange1d: null, - pricePercentChange7d: null, - pricePercentChange14d: null, - pricePercentChange30d: null, - pricePercentChange200d: null, - pricePercentChange1y: null, - } as SpotPrice, - }); - - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: BTC, unit: USD }, - ]); - - expect(result[BTC]?.[USD]?.pricePercentChange).toBeUndefined(); - }); - - it('handles missing asset data correctly by skipping those assets', async () => { - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: UNKNOWN_CRYPTO_1, unit: USD }, - { asset: BTC, unit: USD }, - { asset: UNKNOWN_CRYPTO_2, unit: EUR }, - ]); - - // Should only include BTC since UNKNOWN_CRYPTO_1 and UNKNOWN_CRYPTO_2 don't have price data - expect(result).toStrictEqual({ - [BTC]: { - [USD]: { - fungible: true, - marketCap: '1540421085883.0198', - totalVolume: '23748436299.895576', - circulatingSupply: '19844921', - allTimeHigh: '100847.44951017378', - allTimeLow: '62.86163248290115', - pricePercentChange: { - PT1H: -0.4456714429821922, - P1D: 1.3725526422881404, - P7D: -4.2914380354332256, - P14D: 1.3530761284206316, - P30D: -2.6647248645353425, - P200D: 44.69565022141291, - P1Y: 20.367003699380124, - }, - }, - }, - }); - }); - - it('handles missing unit data correctly by skipping those conversions', async () => { - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: BTC, unit: UNKNOWN_FIAT_1 }, - { asset: BTC, unit: USD }, - { asset: ETH, unit: UNKNOWN_FIAT_2 }, - ]); - - // Should only include BTC->USD since UNKNOWN_FIAT_1 and UNKNOWN_FIAT_2 don't have exchange rates - expect(result).toStrictEqual({ - [BTC]: { - [USD]: { - fungible: true, - marketCap: '1540421085883.0198', - totalVolume: '23748436299.895576', - circulatingSupply: '19844921', - allTimeHigh: '100847.44951017378', - allTimeLow: '62.86163248290115', - pricePercentChange: { - PT1H: -0.4456714429821922, - P1D: 1.3725526422881404, - P7D: -4.2914380354332256, - P14D: 1.3530761284206316, - P30D: -2.6647248645353425, - P200D: 44.69565022141291, - P1Y: 20.367003699380124, - }, - }, - }, - }); - }); - - it('handles zero unit rates correctly by skipping those conversions', async () => { - jest - .spyOn(mockPriceApiClient, 'getMultipleSpotPrices') - .mockResolvedValue({ - [BTC]: { - ...MOCK_SPOT_PRICES[BTC]!, - price: 0, // Zero price for unit - }, - [ETH]: MOCK_SPOT_PRICES[ETH]!, - }); - - const result = await tokenPricesService.getMultipleTokensMarketData([ - { asset: ETH, unit: BTC }, // BTC has zero price, so this should be skipped - { asset: ETH, unit: USD }, - ]); - - // Should only include ETH->USD since BTC has zero price - expect(result).toStrictEqual({ - [ETH]: { - [USD]: { - fungible: true, - marketCap: '208326525244.77222', - totalVolume: '14672129201.423573', - circulatingSupply: '120659504.7581715', - allTimeHigh: '4522.273813243435', - allTimeLow: '0.4013827867691204', - pricePercentChange: { - PT1H: -0.16193070976498064, - P1D: 1.9964598342126199, - P7D: -10.123102834312476, - P14D: -1.7452971064771636, - P30D: -16.78602306244949, - P200D: -21.026646670919543, - P1Y: -47.45246230239663, - }, - }, - }, - }); - }); - }); - - describe('getHistoricalPrice', () => { - it('returns historical prices for a token', async () => { - const result = await tokenPricesService.getHistoricalPrice(BTC, USD); - // We use the same prices for all time periods for simplicity - const expectedPrices = MOCK_HISTORICAL_PRICES.prices.map((price) => [ - price[0], - price[1]!.toString(), - ]); - - expect(result).toStrictEqual({ - intervals: { - P1D: expectedPrices, - P7D: expectedPrices, - P1M: expectedPrices, - P3M: expectedPrices, - P1Y: expectedPrices, - P1000Y: expectedPrices, - }, - updateTime: expect.any(Number), - expirationTime: expect.any(Number), - }); - }); - - it('tracks historical price fetch failures', async () => { - const error = new Error('History failed'); - - jest - .spyOn(mockPriceApiClient, 'getHistoricalPrices') - .mockRejectedValueOnce(error); - - await tokenPricesService.getHistoricalPrice(BTC, USD); - - expect(trackError).toHaveBeenCalledWith(error); - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.ts b/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.ts deleted file mode 100644 index e54da2436..000000000 --- a/packages/solana-wallet-snap/src/core/services/token-prices/TokenPrices.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { CaipAssetTypeStruct } from '@metamask/keyring-api'; -import type { CaipAssetType } from '@metamask/keyring-api'; -import type { Logger } from '@metamask/snap-networks-utils'; -import type { - AssetConversion, - FungibleAssetMarketData, - HistoricalPriceIntervals, -} from '@metamask/snaps-sdk'; -import { assert } from '@metamask/superstruct'; -import { parseCaipAssetType } from '@metamask/utils'; -import BigNumber from 'bignumber.js'; -import { pick } from 'lodash'; - -import type { PriceApiClient } from '../../clients/price-api/PriceApiClient'; -import { - GET_HISTORICAL_PRICES_RESPONSE_NULL_OBJECT, - VsCurrencyParamStruct, -} from '../../clients/price-api/types'; -import type { SpotPrice } from '../../clients/price-api/types'; -import type { FiatTicker } from '../../clients/price-api/types'; -import { trackError } from '../../utils/errors'; -import { isFiat } from '../../utils/isFiat'; -import type { ConfigProvider } from '../config'; -import type { HistoricalPrice } from './types'; - -export class TokenPricesService { - readonly #priceApiClient: PriceApiClient; - - readonly #logger: Logger; - - readonly cacheTtlsMilliseconds: { - fiatExchangeRates: number; - spotPrices: number; - historicalPrices: number; - }; - - constructor({ - configProvider, - priceApiClient, - logger, - }: { - configProvider: ConfigProvider; - priceApiClient: PriceApiClient; - logger: Logger; - }) { - this.#priceApiClient = priceApiClient; - this.#logger = logger; - - const { cacheTtlsMilliseconds } = configProvider.get().priceApi; - this.cacheTtlsMilliseconds = cacheTtlsMilliseconds; - } - - /** - * 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 (!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) => !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; - - if (!result[from]) { - result[from] = {}; - } - - let fromUsdRate: BigNumber; - let toUsdRate: BigNumber; - - if (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 (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, - ) => { - 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 (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 - if (!result[assetType]) { - result[assetType] = {}; - } - - // Store the market data with the unit as the key - result[assetType][unit] = this.#computeMarketData( - cryptoPrices[assetType], - unitUsdRate, - ); - }); - - return result; - } - - async getHistoricalPrice( - from: CaipAssetType, - to: CaipAssetType, - ): Promise { - 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 trackError(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: HistoricalPrice = { - intervals, - updateTime: now, - expirationTime: now + this.cacheTtlsMilliseconds.historicalPrices, - }; - - return result; - } -} diff --git a/packages/solana-wallet-snap/src/core/services/token-prices/types.ts b/packages/solana-wallet-snap/src/core/services/token-prices/types.ts deleted file mode 100644 index fc2e64a2c..000000000 --- a/packages/solana-wallet-snap/src/core/services/token-prices/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { OnAssetHistoricalPriceResponse } from '@metamask/snaps-sdk'; - -export type HistoricalPrice = - NonNullable['historicalPrice']; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/market-data.ts b/packages/solana-wallet-snap/src/core/test/mocks/market-data.ts deleted file mode 100644 index 3cbde9ffd..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/market-data.ts +++ /dev/null @@ -1,243 +0,0 @@ -import type { FungibleAssetMarketData } from '@metamask/snaps-sdk'; -import type { CaipAssetType } from '@metamask/utils'; - -// Common asset types for testing -export const TEST_ASSET_TYPES = { - BTC: 'bip122:000000000019d6689c085ae165831e93/slip44:0' as CaipAssetType, - ETH: 'eip155:1/slip44:60' as CaipAssetType, - SOL: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' as CaipAssetType, - USDC: 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as CaipAssetType, - USD: 'swift:0/iso4217:USD' as CaipAssetType, - EUR: 'swift:0/iso4217:EUR' as CaipAssetType, - GBP: 'swift:0/iso4217:GBP' as CaipAssetType, - SPECIAL_SOL: - 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1/slip44:501' as CaipAssetType, -} as const; - -// ISO 8601 duration constants -export const ISO_DURATIONS = { - PT1H: 'PT1H', - P1D: 'P1D', - P7D: 'P7D', - P14D: 'P14D', - P30D: 'P30D', - P200D: 'P200D', - P1Y: 'P1Y', -} as const; - -// Mock market data for different scenarios -export const MOCK_MARKET_DATA: Record = - { - [TEST_ASSET_TYPES.BTC]: { - fungible: true, - marketCap: '1000000000000', - totalVolume: '50000000000', - circulatingSupply: '19500000', - allTimeHigh: '120000', - allTimeLow: '67.81', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 0.5, - [ISO_DURATIONS.P1D]: 2.1, - [ISO_DURATIONS.P7D]: -1.2, - [ISO_DURATIONS.P30D]: 15.3, - [ISO_DURATIONS.P200D]: 45.1, - [ISO_DURATIONS.P1Y]: 21.4, - }, - }, - [TEST_ASSET_TYPES.ETH]: { - fungible: true, - marketCap: '400000000000', - totalVolume: '20000000000', - circulatingSupply: '120000000', - allTimeHigh: '5000', - allTimeLow: '0.43', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 1.2, - [ISO_DURATIONS.P1D]: 3.5, - [ISO_DURATIONS.P7D]: 5.1, - [ISO_DURATIONS.P30D]: 8.7, - }, - }, - [TEST_ASSET_TYPES.SOL]: { - fungible: true, - marketCap: '80000000000', - totalVolume: '3000000000', - circulatingSupply: '400000000', - allTimeHigh: '260', - allTimeLow: '0.5', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: -0.8, - [ISO_DURATIONS.P1D]: 1.5, - [ISO_DURATIONS.P7D]: -2.3, - }, - }, - [TEST_ASSET_TYPES.USDC]: { - fungible: true, - marketCap: '25000000000', - totalVolume: '1500000000', - circulatingSupply: '25000000000', - allTimeHigh: '1.05', - allTimeLow: '0.95', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 0.01, - [ISO_DURATIONS.P1D]: 0.02, - [ISO_DURATIONS.P7D]: 0.05, - }, - }, - [TEST_ASSET_TYPES.SPECIAL_SOL]: { - fungible: true, - marketCap: '50000000', - totalVolume: '2000000', - circulatingSupply: '1000000', - }, - }; - -// Mock market data for different currencies -export const MOCK_MARKET_DATA_EUR: Record< - CaipAssetType, - FungibleAssetMarketData -> = { - [TEST_ASSET_TYPES.BTC]: { - fungible: true, - marketCap: '850000000000', - totalVolume: '42500000000', - circulatingSupply: '19500000', - allTimeHigh: '102000', - allTimeLow: '57.64', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 0.3, - [ISO_DURATIONS.P1D]: 1.8, - }, - }, - [TEST_ASSET_TYPES.ETH]: { - fungible: true, - marketCap: '340000000000', - totalVolume: '17000000000', - circulatingSupply: '120000000', - allTimeHigh: '4250', - allTimeLow: '0.37', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 1.0, - [ISO_DURATIONS.P1D]: 3.0, - }, - }, -}; - -// Mock market data with minimal fields -export const MOCK_MARKET_DATA_MINIMAL: Record< - CaipAssetType, - FungibleAssetMarketData -> = { - [TEST_ASSET_TYPES.BTC]: { - fungible: true, - marketCap: '1000000000000', - // Missing other fields - }, - [TEST_ASSET_TYPES.ETH]: { - fungible: true, - // Only fungible field - }, -}; - -// Mock market data with only price percent changes -export const MOCK_MARKET_DATA_PRICE_CHANGES_ONLY: Record< - CaipAssetType, - FungibleAssetMarketData -> = { - [TEST_ASSET_TYPES.BTC]: { - fungible: true, - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 0.5, - [ISO_DURATIONS.P1D]: 2.1, - [ISO_DURATIONS.P7D]: -1.2, - }, - }, -}; - -// Mock market data with zero values -export const MOCK_MARKET_DATA_ZERO_VALUES: Record< - CaipAssetType, - FungibleAssetMarketData -> = { - [TEST_ASSET_TYPES.ETH]: { - fungible: true, - marketCap: '0', - totalVolume: '0', - circulatingSupply: '0', - allTimeHigh: '0', - allTimeLow: '0', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 0, - [ISO_DURATIONS.P1D]: 0, - [ISO_DURATIONS.P7D]: 0, - }, - }, -}; - -// Mock market data with very large numbers -export const MOCK_MARKET_DATA_LARGE_NUMBERS: Record< - CaipAssetType, - FungibleAssetMarketData -> = { - [TEST_ASSET_TYPES.BTC]: { - fungible: true, - marketCap: '999999999999999999999999999999', - totalVolume: '123456789012345678901234567890', - circulatingSupply: '21000000', - allTimeHigh: '999999999999999999999999999999', - allTimeLow: '0.000000000000000001', - pricePercentChange: { - [ISO_DURATIONS.PT1H]: 999.99, - [ISO_DURATIONS.P1D]: -999.99, - }, - }, -}; - -// Mock asset request parameters -export const MOCK_ASSET_REQUESTS = { - SINGLE_BTC_USD: [{ asset: TEST_ASSET_TYPES.BTC, unit: TEST_ASSET_TYPES.USD }], - MULTIPLE_CRYPTO_USD: [ - { asset: TEST_ASSET_TYPES.BTC, unit: TEST_ASSET_TYPES.USD }, - { asset: TEST_ASSET_TYPES.ETH, unit: TEST_ASSET_TYPES.USD }, - { asset: TEST_ASSET_TYPES.SOL, unit: TEST_ASSET_TYPES.USD }, - ], - MIXED_CURRENCIES: [ - { asset: TEST_ASSET_TYPES.BTC, unit: TEST_ASSET_TYPES.EUR }, - { asset: TEST_ASSET_TYPES.ETH, unit: TEST_ASSET_TYPES.USD }, - ], - EMPTY: [], - SPECIAL_CHARACTERS: [ - { asset: TEST_ASSET_TYPES.SPECIAL_SOL, unit: TEST_ASSET_TYPES.USD }, - ], -} as const; - -// Helper function to create mock market data for specific assets -export const createMockMarketData = ( - assets: CaipAssetType[], - dataSource: Record = MOCK_MARKET_DATA, -): Record => { - const result: Record = {}; - - for (const asset of assets) { - if (dataSource[asset]) { - result[asset] = dataSource[asset]; - } - } - - return result; -}; - -// Helper function to create mock asset requests -export const createMockAssetRequest = ( - assets: { asset: CaipAssetType; unit: CaipAssetType }[], -) => ({ - assets, -}); - -// Mock error scenarios -export const MOCK_ERRORS = { - NETWORK_TIMEOUT: new Error('Network timeout'), - INVALID_ASSET_TYPE: new Error('Invalid asset type'), - SERVICE_UNAVAILABLE: new Error('Service unavailable'), - RATE_LIMIT_EXCEEDED: new Error('Rate limit exceeded'), -} as const; diff --git a/packages/solana-wallet-snap/src/snapContext.ts b/packages/solana-wallet-snap/src/snapContext.ts index 28e86afd6..6dfc9d683 100644 --- a/packages/solana-wallet-snap/src/snapContext.ts +++ b/packages/solana-wallet-snap/src/snapContext.ts @@ -43,7 +43,6 @@ import { NftService } from './core/services/nft/NftService'; import type { IStateManager } from './core/services/state/IStateManager'; import type { UnencryptedStateValue } from './core/services/state/State'; import { DEFAULT_UNENCRYPTED_STATE, State } from './core/services/state/State'; -import { TokenPricesService } from './core/services/token-prices/TokenPrices'; import { TransactionScanService } from './core/services/transaction-scan/TransactionScan'; import { WalletService } from './core/services/wallet/WalletService'; import logger, { noOpLogger } from './core/utils/logger'; @@ -60,7 +59,6 @@ export type SnapExecutionContext = { priceApiClient: PriceApiClient; state: IStateManager; assetsService: AssetsService; - tokenPricesService: TokenPricesService; signer: Signer; transactionsService: TransactionsService; sendSolBuilder: SendSolBuilder; @@ -138,11 +136,6 @@ const priceApiClient = new PriceApiClient(configProvider, inMemoryCache); const tokenApiClient = new TokenApiClient(configProvider); const nftApiClient = new NftApiClient(configProvider, inMemoryCache); -const tokenPricesService = new TokenPricesService({ - configProvider, - priceApiClient, - logger, -}); const nameResolutionService = new NameResolutionService(connection, logger); const assetsRepository = new AssetsRepository(state); @@ -157,7 +150,6 @@ const snapAssetsAdapter = new SnapAssetsAdapter({ assetsRepository, accountsService, tokenApiClient, - tokenPricesService, cache: inMemoryCache, nftApiClient, }); @@ -277,7 +269,6 @@ const snapContext: SnapExecutionContext = { cache: stateCache, /* Services */ assetsService, - tokenPricesService, signer, transactionsService, sendSolBuilder, @@ -319,7 +310,6 @@ export { subscriptionService, tokenApiClient, tokenHelper, - tokenPricesService, transactionScanService, transactionsService, walletService, From bca4a2bdf8bdf66809c98a8cc0488a6065147478 Mon Sep 17 00:00:00 2001 From: Ulisses Ferreira Date: Thu, 3 Sep 2026 18:34:21 +0100 Subject: [PATCH 3/3] refactor(solana-wallet-snap): remove unused price, state, and mock code --- packages/solana-wallet-snap/CHANGELOG.md | 1 + .../solana-wallet-snap/snap.manifest.json | 8 +- .../src/core/caching/StateCache.test.ts | 781 ------------------ .../src/core/caching/StateCache.ts | 255 ------ .../clients/price-api/PriceApiClient.test.ts | 54 -- .../core/clients/price-api/PriceApiClient.ts | 36 +- .../src/core/clients/price-api/types.ts | 11 - .../components/ActionHeader/ActionHeader.tsx | 38 - .../core/components/Navigation/Navigation.tsx | 29 - .../assets/adapters/SnapAssetsAdapter.test.ts | 7 - .../core/services/config/ConfigProvider.ts | 4 - .../src/core/services/nft/NftService.test.ts | 57 -- .../src/core/services/nft/NftService.ts | 45 - .../src/core/services/state/State.ts | 3 - .../test/mocks/price-api/exchange-rates.ts | 476 ----------- .../core/test/mocks/price-api/spot-prices.ts | 90 -- .../address-1/transaction-2.ts | 204 ----- .../address-1/transaction-3.ts | 115 --- .../address-1/transaction-4.ts | 69 -- .../address-2/transaction-1.ts | 69 -- .../address-2/transaction-2.ts | 69 -- .../address-2/transaction-3.ts | 69 -- .../address-2/transaction-4.ts | 69 -- .../swap-failed-transaction.ts | 427 ---------- .../core/test/mocks/transactions-data/swap.ts | 2 - .../solana-wallet-snap/src/core/types/form.ts | 13 - .../src/core/utils/concurrency.test.ts | 238 ------ .../src/core/utils/concurrency.ts | 114 --- .../src/core/utils/diffArrays.test.ts | 63 -- .../src/core/utils/diffArrays.ts | 26 - .../src/core/utils/diffObjects.test.ts | 87 -- .../src/core/utils/diffObjects.ts | 66 -- .../src/core/utils/formatFiatBalance.test.ts | 35 - .../src/core/utils/formatFiatBalance.ts | 14 - .../utils/getAccountIdFromAddress.test.ts | 27 - .../src/core/utils/getAccountIdFromAddress.ts | 15 - .../core/utils/getClusterFromScope.test.ts | 24 - .../src/core/utils/getClusterFromScope.ts | 17 - .../src/core/utils/isFiat.test.ts | 25 - .../src/core/utils/isFiat.ts | 11 - .../src/core/utils/toTokenUnit.test.ts | 49 -- .../src/core/utils/toTokenUnit.ts | 24 - .../src/core/utils/truncateAddress.test.ts | 27 - .../src/core/utils/truncateAddress.ts | 17 - .../solana-wallet-snap/src/snapContext.ts | 26 - 45 files changed, 3 insertions(+), 3903 deletions(-) delete mode 100644 packages/solana-wallet-snap/src/core/caching/StateCache.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/caching/StateCache.ts delete mode 100644 packages/solana-wallet-snap/src/core/components/ActionHeader/ActionHeader.tsx delete mode 100644 packages/solana-wallet-snap/src/core/components/Navigation/Navigation.tsx delete mode 100644 packages/solana-wallet-snap/src/core/services/nft/NftService.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/services/nft/NftService.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/price-api/exchange-rates.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/price-api/spot-prices.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-2.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-3.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-4.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-1.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-2.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-3.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-4.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap-failed-transaction.ts delete mode 100644 packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap.ts delete mode 100644 packages/solana-wallet-snap/src/core/types/form.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/concurrency.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/concurrency.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/diffArrays.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/diffArrays.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/diffObjects.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/diffObjects.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/formatFiatBalance.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/formatFiatBalance.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/getClusterFromScope.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/getClusterFromScope.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/isFiat.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/isFiat.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/toTokenUnit.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/toTokenUnit.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/truncateAddress.test.ts delete mode 100644 packages/solana-wallet-snap/src/core/utils/truncateAddress.ts diff --git a/packages/solana-wallet-snap/CHANGELOG.md b/packages/solana-wallet-snap/CHANGELOG.md index 0fc7356bd..4fd4a80dd 100644 --- a/packages/solana-wallet-snap/CHANGELOG.md +++ b/packages/solana-wallet-snap/CHANGELOG.md @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING** Remove the `onAssetsLookup`, `onAssetsConversion`, `onAssetHistoricalPrice`, and `onAssetsMarketData` asset handler entry points, along with the now-unused handler modules and the `endowment:assets` permission ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) - **BREAKING** Remove `AssetsService.fetchAssetsMarketData` and `SnapAssetsAdapter.fetchAssetsMarketData`, the `TokenPricesService` class, and `PriceApiClient.getHistoricalPrices`, all of which were only used by the removed asset handlers ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) +- Remove the now-unused `PriceApiClient.getFiatExchangeRates` method and related `ExchangeRate` type, the unused `tokenPrices` unencrypted state field, the unused `fiatExchangeRates` and `historicalPrices` price API cache TTL options, and unused price API test mocks ([#261](https://github.com/MetaMask/internal-snaps/pull/261)) ### Fixed diff --git a/packages/solana-wallet-snap/snap.manifest.json b/packages/solana-wallet-snap/snap.manifest.json index fd1ae29b5..37e4cf74a 100644 --- a/packages/solana-wallet-snap/snap.manifest.json +++ b/packages/solana-wallet-snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/internal-snaps.git" }, "source": { - "shasum": "VuEc1ZDr6riFrfV+t9xCsJCpEcePJgaADPjwoDAQp7w=", + "shasum": "5XRlmv4jiAX3IKwm1vWkTlTRvieyZ4yBBgbQvDbXMQc=", "location": { "npm": { "filePath": "dist/bundle.js", @@ -72,12 +72,6 @@ } } }, - "endowment:assets": { - "scopes": [ - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", - "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" - ] - }, "endowment:name-lookup": { "chains": [ "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", diff --git a/packages/solana-wallet-snap/src/core/caching/StateCache.test.ts b/packages/solana-wallet-snap/src/core/caching/StateCache.test.ts deleted file mode 100644 index 8e85b1d62..000000000 --- a/packages/solana-wallet-snap/src/core/caching/StateCache.test.ts +++ /dev/null @@ -1,781 +0,0 @@ -/* eslint-disable jest/prefer-strict-equal */ - -import { InMemoryState } from '../services/state/InMemoryState'; -import { StateCache } from './StateCache'; - -describe('StateCache', () => { - describe('constructor', () => { - it('uses the default prefix if not specified', () => { - const cache = new StateCache(new InMemoryState({})); - - expect(cache.prefix).toBe('__cache__default'); - }); - - it('uses the specified prefix if provided', () => { - const cache = new StateCache( - new InMemoryState({}), - undefined, - '__cache__my-prefix', - ); - - expect(cache.prefix).toBe('__cache__my-prefix'); - }); - }); - - describe('get', () => { - it('returns undefined if the cache is not initialized', async () => { - const stateWithNoCache = new InMemoryState({ - name: 'John', // State has some data that is not related to the cache - // __cache__default: {} // State has not been initialized with cached data - }); - const cache = new StateCache(stateWithNoCache); - - const value = await cache.get('someKey'); - - expect(value).toBeUndefined(); - }); - - it('returns undefined if the cache is initialized but the key is not present', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const value = await cache.get('someOtherKey'); - - expect(value).toBeUndefined(); - }); - - it('returns the cached value if the cache is initialized and the key is present and not expired', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, // Expires in a long time - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const value = await cache.get('someKey'); - - expect(value).toBe('someValue'); - }); - - it('returns undefined if the cache is initialized and the key is present but expired', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const value = await cache.get('someKey'); - - expect(value).toBeUndefined(); - }); - - it('deletes expired cache entries upon retrieval', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.get('someKey'); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: {}, - }); - }); - }); - - describe('set', () => { - it('initializes the cache if it is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.set('someKey', 'someValue'); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - }); - - it('sets the cache entry with no expiration if no ttl is provided', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - - await cache.set('someKey', 'someValue'); - const stateValue = await stateWithCache.get(); - - const value = await cache.get('someKey'); - - expect(value).toBe('someValue'); - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - }); - - it('overwrites the cache entry if it is present', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.set('someKey', 'someOtherValue'); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - }); - - it('sets the cache entry with the provided ttl', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - jest.spyOn(Date, 'now').mockReturnValueOnce(1704067200000); // January 1, 2024 - - await cache.set('someKey', 'someValue', 1000); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067201000, // January 1, 2024 + 1 second - }, - }, - }); - }); - - it('supports a ttl of 0', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - jest - .spyOn(Date, 'now') - .mockReturnValueOnce(1704067200000) // January 1, 2024 - .mockReturnValueOnce(1704067200001); // January 1, 2024 + 1 millisecond - - await cache.set('someKey', 'someValue', 0); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 (+ 0 seconds) - }, - }, - }); - - const value = await cache.get('someKey'); // Should expire immediately - expect(value).toBeUndefined(); - }); - - it('throws an error if the ttl is not a number', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - - await expect( - cache.set('someKey', 'someValue', 'not a number' as unknown as number), - ).rejects.toThrow('TTL must be a number'); - }); - - it('throws an error if the ttl is negative', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - - await expect(cache.set('someKey', 'someValue', -1)).rejects.toThrow( - 'TTL must be positive', - ); - }); - - it('throws an error if the ttl is too large', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: {}, - }); - const cache = new StateCache(stateWithCache); - - await expect( - cache.set('someKey', 'someValue', Number.MAX_SAFE_INTEGER + 1), - ).rejects.toThrow('TTL must be less than 2^53 - 1'); - }); - }); - - describe('delete', () => { - it('deletes the cache entry and returns true if the entry was present', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.delete('someKey'); - expect(result).toBe(true); - - const value = await cache.get('someKey'); - - expect(value).toBeUndefined(); - }); - - it('leaves the cache unchanged and returns false if the entry was not present', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.delete('someOtherKey'); // Try to - const someKeyValue = await cache.get('someKey'); - const someOtherKeyValue = await cache.get('someOtherKey'); - - expect(result).toBe(false); - expect(someKeyValue).toBe('someValue'); - expect(someOtherKeyValue).toBeUndefined(); - }); - }); - - describe('clear', () => { - it('empties the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - createdAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.clear(); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: {}, - }); - }); - - it('does not throw an error if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.clear(); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: {}, - }); - }); - }); - - describe('has', () => { - it('returns true if the key is present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.has('someKey'); - - expect(result).toBe(true); - }); - - it('returns false if the key is not present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.has('someOtherKey'); - expect(result).toBe(false); - }); - - it('does not throw an error if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.has('someKey'); - expect(result).toBe(false); - }); - }); - - describe('keys', () => { - it('returns all keys in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.keys(); - - expect(result).toStrictEqual(['someKey', 'someOtherKey']); - }); - - it('returns an empty array if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.keys(); - - expect(result).toStrictEqual([]); - }); - }); - - describe('size', () => { - it('returns the number of items in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.size(); - - expect(result).toBe(2); - }); - - it('returns 0 if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.size(); - - expect(result).toBe(0); - }); - }); - - describe('peek', () => { - it('returns the value of an unexpired key if it is present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.peek('someKey'); - - expect(result).toBe('someValue'); - }); - - it('returns the value of an expired key if it is present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.peek('someKey'); - - expect(result).toBe('someValue'); - }); - - it('returns undefined if the key is not present in the cache', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.peek('someOtherKey'); - - expect(result).toBeUndefined(); - }); - - it('does not throw an error if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.peek('someKey'); - expect(result).toBeUndefined(); - }); - }); - - describe('mget', () => { - it('returns the values of the keys if they are present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.mget(['someKey', 'someOtherKey']); - - expect(result).toStrictEqual({ - someKey: 'someValue', - someOtherKey: 'someOtherValue', - }); - }); - - it('returns undefined for keys that are not present in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.mget(['someKey', 'someOtherKey']); - - expect(result).toEqual({ - someKey: 'someValue', - someOtherKey: undefined, - }); - }); - - it('returns undefined for keys that are expired', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.mget(['someKey']); - - expect(result).toEqual({ - someKey: undefined, - }); - }); - - it('returns an empty object if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.mget(['someKey', 'someOtherKey']); - - expect(result).toStrictEqual({}); - }); - - it('deletes expired cache entries upon retrieval', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: 1704067200000, // January 1, 2024 - }, - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.mget(['someKey']); - const stateValue = await stateWithCache.get(); - - expect(stateValue).toStrictEqual({ - __cache__default: { - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - }); - }); - - describe('mset', () => { - it('sets the values of the keys if they are present in the cache', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.mset([ - { key: 'someKey', value: 'someValue' }, - { key: 'someOtherKey', value: 'someOtherValue' }, - ]); - - const result = await cache.mget(['someKey', 'someOtherKey']); - - expect(result).toStrictEqual({ - someKey: 'someValue', - someOtherKey: 'someOtherValue', - }); - }); - - it('does not store undefined values in the cache', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.mset([ - { key: 'someKey', value: 'someValue' }, - { key: 'undefinedKey', value: undefined }, - ]); - - const result = await cache.mget(['someKey', 'undefinedKey']); - - expect(result).toEqual({ - someKey: 'someValue', - undefinedKey: undefined, - }); - - // Verify the undefined value was not stored in the cache - const stateValue = await stateWithCache.get(); - expect(stateValue).toStrictEqual({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - }); - - it('stores null values in the cache', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.mset([{ key: 'someKey', value: null }]); - - const result = await cache.mget(['someKey']); - - expect(result).toStrictEqual({ - someKey: null, - }); - }); - - it('does not throw an error if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await cache.mset([{ key: 'someKey', value: 'someValue' }]); - - const result = await cache.mget(['someKey']); - - expect(result).toStrictEqual({ - someKey: 'someValue', - }); - }); - - it('throws an error if the ttl is invalid', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - await expect( - cache.mset([ - { - key: 'someKey', - value: 'someValue', - ttlMilliseconds: 'not a number' as unknown as number, - }, - ]), - ).rejects.toThrow('TTL must be a number'); - }); - - it('does not affect other keys in the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey0: { - value: 'someValue0', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - someKey1: { - value: 'someValue1', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.mset([ - { key: 'someKey0', value: 'someValue0Overwritten' }, - { key: 'someKey2', value: 'someValue2' }, - ]); - - const result = await cache.mget(['someKey0', 'someKey1', 'someKey2']); - - expect(result).toStrictEqual({ - someKey0: 'someValue0Overwritten', - someKey1: 'someValue1', - someKey2: 'someValue2', - }); - }); - - it('no-ops if no entries are provided', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - const updateSpy = jest.spyOn(stateWithCache, 'update'); - - await cache.mset([]); - - expect(updateSpy).not.toHaveBeenCalled(); - }); - - it('defers to set if there is only one entry', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - const setSpy = jest.spyOn(cache, 'set'); - - const singleEntry = { - key: 'someKey', - value: 'someValue', - ttlMilliseconds: 1000, - }; - await cache.mset([singleEntry]); - - expect(setSpy).toHaveBeenCalledWith( - singleEntry.key, - singleEntry.value, - singleEntry.ttlMilliseconds, - ); - }); - }); - - describe('mdelete', () => { - it('deletes the keys from the cache', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - someOtherKey: { - value: 'someOtherValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - await cache.mdelete(['someKey', 'someOtherKey']); - - const result = await cache.mget(['someKey', 'someOtherKey']); - - expect(result).toEqual({ - someKey: undefined, - someOtherKey: undefined, - }); - }); - - it('returns an object where the values are true if the keys were deleted and false if they were not present', async () => { - const stateWithCache = new InMemoryState({ - __cache__default: { - someKey: { - value: 'someValue', - expiresAt: Number.MAX_SAFE_INTEGER, - }, - }, - }); - const cache = new StateCache(stateWithCache); - - const result = await cache.mdelete(['someKey', 'someOtherKey']); - - expect(result).toStrictEqual({ - someKey: true, - someOtherKey: false, - }); - }); - - it('does not throw an error if the cache is not initialized', async () => { - const stateWithCache = new InMemoryState({}); - const cache = new StateCache(stateWithCache); - - const result = await cache.mdelete(['someKey', 'someOtherKey']); - - expect(result).toStrictEqual({ - someKey: false, - someOtherKey: false, - }); - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/caching/StateCache.ts b/packages/solana-wallet-snap/src/core/caching/StateCache.ts deleted file mode 100644 index 598af6d8c..000000000 --- a/packages/solana-wallet-snap/src/core/caching/StateCache.ts +++ /dev/null @@ -1,255 +0,0 @@ -import type { Logger, Serializable } from '@metamask/snap-networks-utils'; -import { assert } from '@metamask/utils'; - -import type { IStateManager } from '../services/state/IStateManager'; -import defaultLogger from '../utils/logger'; -import type { ICache } from './ICache'; -import type { CacheEntry } from './types'; - -/** - * The whole cache store. - */ -export type CacheStore = Record | undefined; - -/** - * A prefix for the cache "location" in the state. Enforced to start with `__cache__` to avoid collisions with other state values. - */ -export type CachePrefix = `__cache__${string}`; - -/** - * Describes the shape of the whole state inside which the cache is stored. - */ -export type StateValue = { - [x: string]: Serializable; -} & { - [K in CachePrefix]?: CacheStore; -}; - -/** - * A cache that wraps any implementation of the `IStateManager` interface to store the cache. - * - * It is intended to be used with the snap's `State` class, but can be used with any other implementation of the `IStateManager` interface. For instance it can be used with the `InMemoryState` class for testing purposes. - * - * By default, it stores its data in the `__cache__default` property of the state, but you can specify any other prefix you want, provided it starts with `__cache__` to avoid collisions with other state values. - * This is useful if you want to have multiple independent caches in the same state. - * - * ``` - * { - * ..., // other state values - * __cache__default: { - * key1: value1, - * key2: value2, - * }, - * __cache__my-prefix: { - * key3: value3, - * key4: value4, - * }, - * } - * ``` - * - * @example - * ```ts - * const state = new State({}); // Here we use the real snap's state - * const cache = new StateCache(state, '__cache__my-prefix'); - * - * // state looks like this: - * // { - * // ..., // other state values - * // no __cache__my-prefix yet - * // } - * - * await cache.set('key1', 'value1'); - * - * // state looks like this: - * // { - * // ..., // other state values - * // __cache__my-prefix: { - * // key1: value1, - * // }, - * // } - * ``` - */ -export class StateCache implements ICache { - readonly #state: IStateManager; - - public readonly prefix: CachePrefix; - - public readonly logger: Logger; - - constructor( - state: IStateManager, - logger: Logger = defaultLogger, - prefix: CachePrefix = '__cache__default', - ) { - this.#state = state; - this.logger = logger; - this.prefix = prefix; - } - - async get(key: string): Promise { - const result = await this.mget([key]); - return result[key]; - } - - async set( - key: string, - value: Serializable, - ttlMilliseconds = Number.MAX_SAFE_INTEGER, - ): Promise { - this.#validateTtlOrThrow(ttlMilliseconds); - - await this.#state.setKey(`${this.prefix}.${key}`, { - value, - expiresAt: Math.min( - Date.now() + (ttlMilliseconds ?? Number.MAX_SAFE_INTEGER), - Number.MAX_SAFE_INTEGER, - ), - }); - } - - #validateTtlOrThrow(ttlMilliseconds?: number): void { - if (ttlMilliseconds === undefined) { - return; - } - - if (typeof ttlMilliseconds !== 'number') { - throw new Error('TTL must be a number'); - } - - if (ttlMilliseconds < 0) { - throw new Error('TTL must be positive'); - } - - if (ttlMilliseconds > Number.MAX_SAFE_INTEGER) { - throw new Error('TTL must be less than 2^53 - 1'); - } - } - - async delete(key: string): Promise { - const result = await this.mdelete([key]); - return result[key] ?? false; - } - - async clear(): Promise { - await this.#state.setKey(this.prefix, {}); - } - - async has(key: string): Promise { - const result = await this.get(key); - return result !== undefined; - } - - async keys(): Promise { - const cacheStore = await this.#state.getKey(this.prefix); - - return Object.keys(cacheStore ?? {}); - } - - async size(): Promise { - const cacheStore = await this.#state.getKey(this.prefix); - - return Object.keys(cacheStore ?? {}).length; - } - - async peek(key: string): Promise { - const cacheStore = await this.#state.getKey(this.prefix); - const cacheEntry = cacheStore?.[key]; - - return cacheEntry?.value; - } - - async mget( - keys: string[], - ): Promise> { - const cacheStore = await this.#state.getKey(this.prefix); - - const keysAndValues = Object.entries(cacheStore ?? {}).filter(([key]) => - keys.includes(key), - ); - - const expiredKeys = keysAndValues.filter( - ([_, cacheEntry]) => cacheEntry && cacheEntry.expiresAt < Date.now(), - ); - - await this.mdelete(expiredKeys.map(([key]) => key)); - - return keysAndValues.reduce>( - (acc, [key, cacheEntry]) => { - if (cacheEntry === undefined) { - this.logger.info(`[StateCache] ❌ Cache miss for key "${key}"`); - return acc; - } - - if (cacheEntry.expiresAt < Date.now()) { - this.logger.info(`[StateCache] ⌛ Cache expired for key "${key}"`); - acc[key] = undefined; - } else { - this.logger.info(`[StateCache] 🎉 Cache hit for key "${key}"`); - acc[key] = cacheEntry.value; - } - - return acc; - }, - {}, - ); - } - - async mset( - entries: { key: string; value: Serializable; ttlMilliseconds?: number }[], - ): Promise { - if (entries.length === 0) { - return; - } - - if (entries.length === 1) { - assert(entries[0]); // Enforce type narrowing as TS cannot infer that entries[0] is defined - const { key, value, ttlMilliseconds } = entries[0]; - await this.set(key, value, ttlMilliseconds); - return; - } - - entries.forEach(({ ttlMilliseconds }) => { - this.#validateTtlOrThrow(ttlMilliseconds); - }); - - // Using `state.update` is preferred for bulk `set`s, because it's more efficient and atomic. - await this.#state.update((stateValue) => { - const cacheStore = stateValue[this.prefix] ?? {}; - entries.forEach(({ key, value, ttlMilliseconds }) => { - if (value === undefined) { - return; - } - cacheStore[key] = { - value, - expiresAt: Math.min( - Date.now() + (ttlMilliseconds ?? Number.MAX_SAFE_INTEGER), - Number.MAX_SAFE_INTEGER, - ), - }; - }); - stateValue[this.prefix] = cacheStore; - return stateValue; - }); - } - - async mdelete(keys: string[]): Promise> { - const result: Record = {}; - - // Using `state.update` is preferred for bulk `delete`s, because it's more efficient and atomic. - await this.#state.update((stateValue) => { - const cacheStore = stateValue[this.prefix] ?? {}; - keys.forEach((key) => { - if (cacheStore[key] === undefined) { - result[key] = false; - } else { - delete cacheStore[key]; - result[key] = true; - } - }); - stateValue[this.prefix] = cacheStore; - return stateValue; - }); - - return result; - } -} diff --git a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts index 6a34946a0..b1c066a88 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.test.ts @@ -8,7 +8,6 @@ import { InMemoryCache } from '../../caching/InMemoryCache'; import { KnownCaip19Id } from '../../constants/solana'; import { mockLogger } from '../../services/__mocks__/logger'; import type { ConfigProvider } from '../../services/config'; -import { MOCK_EXCHANGE_RATES } from '../../test/mocks/price-api/exchange-rates'; import { MOCK_SPOT_PRICES } from './mocks/spot-prices'; import { PriceApiClient } from './PriceApiClient'; import type { SpotPrices, VsCurrencyParam } from './types'; @@ -27,9 +26,7 @@ describe('PriceApiClient', () => { baseUrl: 'https://some-mock-url.com', chunkSize: 50, cacheTtlsMilliseconds: { - fiatExchangeRates: 0, spotPrices: 0, - historicalPrices: 0, }, }, }), @@ -45,55 +42,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('caches the fiat exchange rates', async () => { - // TTL 0 expires on the next clock tick (`expiresAt < Date.now()`), so the - // second call can miss the cache and hit an exhausted fetch mock. - const cachingClient = new PriceApiClient( - { - get: jest.fn().mockReturnValue({ - priceApi: { - baseUrl: 'https://some-mock-url.com', - chunkSize: 50, - cacheTtlsMilliseconds: { - fiatExchangeRates: 60_000, - spotPrices: 0, - historicalPrices: 0, - }, - }, - }), - } as unknown as ConfigProvider, - mockCache, - mockFetch, - mockLogger, - ); - - mockFetch.mockResolvedValueOnce({ - ok: true, - json: jest.fn().mockResolvedValueOnce(MOCK_EXCHANGE_RATES), - }); - - await cachingClient.getFiatExchangeRates(); - await cachingClient.getFiatExchangeRates(); - - expect(mockFetch).toHaveBeenCalledTimes(1); - }); - }); - describe('getMultipleSpotPrices', () => { const mockResponse: SpotPrices = { [KnownCaip19Id.SolMainnet]: MOCK_SPOT_PRICES[KnownCaip19Id.SolMainnet]!, @@ -289,9 +237,7 @@ describe('PriceApiClient', () => { baseUrl: 'invalid-url', chunkSize: 50, cacheTtlsMilliseconds: { - fiatExchangeRates: 0, spotPrices: 0, - historicalPrices: 0, }, }, }), diff --git a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts index 5274c6c52..f7f94189e 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/PriceApiClient.ts @@ -8,10 +8,9 @@ import { CaipAssetTypeStruct } from '@metamask/utils'; import { mapKeys } from 'lodash'; import type { ICache } from '../../caching/ICache'; -import { useCache } from '../../caching/useCache'; import type { ConfigProvider } from '../../services/config'; import logger from '../../utils/logger'; -import type { ExchangeRate, FiatTicker, SpotPrices, VsCurrencyParam } from './types'; +import type { SpotPrices, VsCurrencyParam } from './types'; import { SpotPricesStruct, VsCurrencyParamStruct } from './types'; export class PriceApiClient { @@ -26,9 +25,7 @@ export class PriceApiClient { readonly #cache: ICache; readonly cacheTtlsMilliseconds: { - fiatExchangeRates: number; spotPrices: number; - historicalPrices: number; }; constructor( @@ -51,37 +48,6 @@ export class PriceApiClient { this.#cache = _cache; } - async getFiatExchangeRates(): Promise> { - return useCache( - this.#getFiatExchangeRates_INTERNAL.bind(this), - this.#cache, - { - functionName: 'PriceApiClient:getFiatExchangeRates', - ttlMilliseconds: this.cacheTtlsMilliseconds.fiatExchangeRates, - }, - )(); - } - - async #getFiatExchangeRates_INTERNAL(): Promise< - Record - > { - try { - const response = await this.#fetch( - `${this.#baseUrl}/v1/exchange-rates/fiat`, - ); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - return data; - } catch (error) { - this.#logger.error(error, 'Error fetching fiat exchange rates'); - throw error; - } - } - /** * Business logic for `getMultipleSpotPrices`. * diff --git a/packages/solana-wallet-snap/src/core/clients/price-api/types.ts b/packages/solana-wallet-snap/src/core/clients/price-api/types.ts index b3029d658..abb023eb3 100644 --- a/packages/solana-wallet-snap/src/core/clients/price-api/types.ts +++ b/packages/solana-wallet-snap/src/core/clients/price-api/types.ts @@ -13,10 +13,6 @@ import { } from '@metamask/superstruct'; import { CaipAssetTypeStruct } from '@metamask/utils'; -export type PriceApiClientConfig = { - baseUrl: string; -}; - export const CryptoTickerStruct = enums([ 'btc', 'eth', @@ -113,13 +109,6 @@ export const TickerStruct = union([ export type Ticker = Infer; -export type ExchangeRate = { - name: string; - ticker: Ticker; - value: number; - currencyType: 'fiat' | 'crypto' | 'commodity'; -}; - /** * 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). diff --git a/packages/solana-wallet-snap/src/core/components/ActionHeader/ActionHeader.tsx b/packages/solana-wallet-snap/src/core/components/ActionHeader/ActionHeader.tsx deleted file mode 100644 index 8a0c09d9e..000000000 --- a/packages/solana-wallet-snap/src/core/components/ActionHeader/ActionHeader.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Box, Heading, Image, Spinner, Text } from '@metamask/snaps-sdk/jsx'; - -export type ActionHeaderProps = { - title: string; - subtitle: string; - iconSrc?: string; - isLoading?: boolean; -}; - -/** - * ActionHeader component. - * - * @param props - The props for the ActionHeader component. - * @param props.title - The title of the action header. - * @param props.subtitle - The subtitle of the action header. - * @param props.iconSrc - The icon source to display. The Snaps `Image` component supports both SVG strings and image URLs (e.g. PNG/JPEG). - * @param props.isLoading - Renders a spinner IN PLACE OF THE ICON if true. - * @returns The ActionHeader component. - */ -export const ActionHeader = ({ - title, - subtitle, - iconSrc, - isLoading, -}: ActionHeaderProps) => { - return ( - - - {isLoading ? : null} - {iconSrc && !isLoading ? ( - - ) : null} - - {title} - {subtitle ? {subtitle} : null} - - ); -}; diff --git a/packages/solana-wallet-snap/src/core/components/Navigation/Navigation.tsx b/packages/solana-wallet-snap/src/core/components/Navigation/Navigation.tsx deleted file mode 100644 index fd80c11db..000000000 --- a/packages/solana-wallet-snap/src/core/components/Navigation/Navigation.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Box, Button, Heading, Icon } from '@metamask/snaps-sdk/jsx'; -import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; - -type NavigationProps = { - title: string; - backButtonName?: string; -}; - -export const Navigation: SnapComponent = ({ - title, - backButtonName, -}) => { - return ( - - {backButtonName ? ( - - ) : null} - {title} - - {null} - {null} - {null} - {null} - - - ); -}; diff --git a/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts b/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts index 1f9be1274..90c471622 100644 --- a/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts +++ b/packages/solana-wallet-snap/src/core/services/assets/adapters/SnapAssetsAdapter.test.ts @@ -16,7 +16,6 @@ import { createMockConnection } from '../../__mocks__/mockConnection'; import type { AccountsService } from '../../accounts/AccountsService'; import type { ConfigProvider } from '../../config'; import type { SolanaConnection } from '../../connection'; -import type { TokenPricesService } from '../../token-prices/TokenPrices'; import type { AssetsRepository } from '../AssetsRepository'; import { SnapAssetsAdapter } from './SnapAssetsAdapter'; @@ -27,7 +26,6 @@ describe('SnapAssetsAdapter', () => { let mockAssetsRepository: AssetsRepository; let mockAccountsService: AccountsService; let mockTokenApiClient: TokenApiClient; - let mockTokenPricesService: TokenPricesService; let mockNftApiClient: NftApiClient; let mockCache: ICache; @@ -43,10 +41,6 @@ describe('SnapAssetsAdapter', () => { getTokensMetadata: jest.fn().mockResolvedValue({}), } as unknown as TokenApiClient; - mockTokenPricesService = { - getMultipleTokensMarketData: jest.fn().mockResolvedValue({}), - } as unknown as TokenPricesService; - mockCache = new InMemoryCache(mockLogger); mockNftApiClient = { @@ -72,7 +66,6 @@ describe('SnapAssetsAdapter', () => { assetsRepository: mockAssetsRepository, accountsService: mockAccountsService, tokenApiClient: mockTokenApiClient, - tokenPricesService: mockTokenPricesService, cache: mockCache, nftApiClient: mockNftApiClient, }); diff --git a/packages/solana-wallet-snap/src/core/services/config/ConfigProvider.ts b/packages/solana-wallet-snap/src/core/services/config/ConfigProvider.ts index 423467755..ab6742d46 100644 --- a/packages/solana-wallet-snap/src/core/services/config/ConfigProvider.ts +++ b/packages/solana-wallet-snap/src/core/services/config/ConfigProvider.ts @@ -71,9 +71,7 @@ export type Config = { baseUrl: string; chunkSize: number; cacheTtlsMilliseconds: { - fiatExchangeRates: number; spotPrices: number; - historicalPrices: number; }; }; tokenApi: { @@ -184,9 +182,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/solana-wallet-snap/src/core/services/nft/NftService.test.ts b/packages/solana-wallet-snap/src/core/services/nft/NftService.test.ts deleted file mode 100644 index afc7396aa..000000000 --- a/packages/solana-wallet-snap/src/core/services/nft/NftService.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { mockLogger } from '../__mocks__/logger'; -import type { SolanaConnection } from '../connection'; -import { NftService } from './NftService'; - -describe.skip('NftService', () => { - let service: NftService; - let mockConnection: SolanaConnection; - - beforeEach(() => { - mockConnection = { - getRpc: jest.fn().mockReturnValue({ - getAccountInfo: jest.fn().mockReturnValue({ - send: jest.fn(), - }), - }), - } as unknown as SolanaConnection; - - service = new NftService(mockConnection, mockLogger); - }); - - describe('isMaybeNonFungible', () => { - it('returns true for tokens with 0 decimals', () => { - const token = { - tokenAmount: { - decimals: 0, - }, - }; - - const result = NftService.isMaybeNonFungible(token); - expect(result).toBe(true); - }); - - it('returns false for tokens with non-zero decimals', () => { - const token = { - tokenAmount: { - decimals: 6, - }, - }; - - const result = NftService.isMaybeNonFungible(token); - expect(result).toBe(false); - }); - - it('works with various shapes of data, provided it has tokenAmount.decimals', () => { - const token = { - tokenAmount: { - decimals: 0, - amount: '1000', - otherField: 'value', - }, - }; - - const result = NftService.isMaybeNonFungible(token); - expect(result).toBe(true); - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/services/nft/NftService.ts b/packages/solana-wallet-snap/src/core/services/nft/NftService.ts deleted file mode 100644 index 01db1262c..000000000 --- a/packages/solana-wallet-snap/src/core/services/nft/NftService.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { Logger } from '@metamask/snap-networks-utils'; - -import logger from '../../utils/logger'; -import type { SolanaConnection } from '../connection'; - -export class NftService { - readonly #connection: SolanaConnection; - - readonly #logger: Logger; - - constructor(connection: SolanaConnection, _logger: Logger = logger) { - this.#connection = connection; - this.#logger = _logger; - } - - /** - * A quick and synchronous way to check if a token is non-fungible, i.e. it's an NFT. - * - * ⚠️ WARNING: This is NOT a 100% reliable way to check if a token is an NFT, just - * that there's a good chance that the token is an NFT. - * - * It only checks if the token has 0 decimals, which is a common but not exclusive - * characteristic of NFTs. A token with 0 decimals could still be a fungible token - * with a supply greater than 1. - * - * Use cases: - * - Quick filtering of potential NFTs in UI lists. - * - Initial screening before performing full NFT validation. - * - Situations where performance is critical and absolute accuracy isn't required. - * - * @param token - The token account to check. Must contain token amount information. - * @param token.tokenAmount - The token amount object containing decimals information. - * @param token.tokenAmount.decimals - The number of decimal places the token uses. - * @returns True if the token has 0 decimals (potential NFT), false otherwise. - */ - static isMaybeNonFungible< - TToken extends { tokenAmount: { decimals: number } }, - >(token: TToken): boolean { - const { tokenAmount } = token; - const { decimals } = tokenAmount; - - // return decimals === 0; - return false; - } -} diff --git a/packages/solana-wallet-snap/src/core/services/state/State.ts b/packages/solana-wallet-snap/src/core/services/state/State.ts index ff42e9867..18d6ab881 100644 --- a/packages/solana-wallet-snap/src/core/services/state/State.ts +++ b/packages/solana-wallet-snap/src/core/services/state/State.ts @@ -19,7 +19,6 @@ import type { Subscription, } from '../../../entities'; import type { EventEmitter } from '../../../infrastructure'; -import type { SpotPrices } from '../../clients/price-api/types'; import type { IStateManager } from './IStateManager'; export type AccountId = string; @@ -32,7 +31,6 @@ export type UnencryptedStateValue = { // to keep track of the transactions per account. The field transactions above only stores non-spam transactions, which break the refreshAccounts cronjob logic. signatures: Record; assetEntities: Record; - tokenPrices: SpotPrices; subscriptions: Record; webSocketConnections: { closeWebSocketConnectionsBackgroundEventId: string | null; @@ -45,7 +43,6 @@ export const DEFAULT_UNENCRYPTED_STATE: UnencryptedStateValue = { transactions: {}, signatures: {}, assetEntities: {}, - tokenPrices: {}, subscriptions: {}, webSocketConnections: { closeWebSocketConnectionsBackgroundEventId: null, diff --git a/packages/solana-wallet-snap/src/core/test/mocks/price-api/exchange-rates.ts b/packages/solana-wallet-snap/src/core/test/mocks/price-api/exchange-rates.ts deleted file mode 100644 index 8bb336df4..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/price-api/exchange-rates.ts +++ /dev/null @@ -1,476 +0,0 @@ -import type { ExchangeRate, Ticker } from '../../../clients/price-api/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/solana-wallet-snap/src/core/test/mocks/price-api/spot-prices.ts b/packages/solana-wallet-snap/src/core/test/mocks/price-api/spot-prices.ts deleted file mode 100644 index 4d24f92c5..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/price-api/spot-prices.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * HEADS UP! Changing this mock MUST involve changing the exchange rates mock too! - * Their values are interdependent and essential for the TokenPricesService tests. - */ -export const MOCK_SPOT_PRICES = { - 'bip122:000000000019d6689c085ae165831e93/slip44:0': { - id: 'bitcoin', - price: 108383, - marketCap: 2153703484251, - allTimeHigh: 111814, - allTimeLow: 67.81, - totalVolume: 26490441505, - high1d: 108312, - low1d: 105402, - circulatingSupply: 19886487, - dilutedMarketCap: 2153703484251, - marketCapPercentChange1d: 2.32194, - priceChange1d: 2558.06, - pricePercentChange1h: 0.3843563748092404, - pricePercentChange1d: 2.417256898831376, - pricePercentChange7d: 0.5848420826167852, - pricePercentChange14d: 3.573582647113796, - pricePercentChange30d: 4.0364417287116305, - pricePercentChange200d: 6.841043820722927, - pricePercentChange1y: 74.83031943795173, - }, - 'eip155:1/slip44:60': { - id: 'ethereum', - price: 2472.85, - marketCap: 298533579846, - allTimeHigh: 4878.26, - allTimeLow: 0.432979, - totalVolume: 9053920577, - high1d: 2473.06, - low1d: 2393.31, - circulatingSupply: 120717388.8264203, - dilutedMarketCap: 298533579846, - marketCapPercentChange1d: 2.15855, - priceChange1d: 52.3, - pricePercentChange1h: 0.7092921207897362, - pricePercentChange1d: 2.160678145136992, - pricePercentChange7d: 1.7425998170003503, - pricePercentChange14d: -1.2732287255912829, - pricePercentChange30d: -2.09981500745285, - pricePercentChange200d: -36.383873763555656, - pricePercentChange1y: -27.526564808866777, - }, - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { - id: 'solana', - price: 150.62, - marketCap: 80423231441, - allTimeHigh: 293.31, - allTimeLow: 0.500801, - totalVolume: 3556506112, - high1d: 150.43, - low1d: 145.46, - circulatingSupply: 534608592.310483, - dilutedMarketCap: 90908091780, - marketCapPercentChange1d: 2.29743, - priceChange1d: 3.56, - pricePercentChange1h: 0.8121844458320602, - pricePercentChange1d: 2.421143543804292, - pricePercentChange7d: 3.176470205229667, - pricePercentChange14d: 3.6015257116898223, - pricePercentChange30d: -1.7218014883767463, - pricePercentChange200d: -32.14283758271846, - pricePercentChange1y: 1.6989732581584913, - }, - 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': { - id: 'usd-coin', - price: 0.999884, - marketCap: 61656570570, - allTimeHigh: 1.17, - allTimeLow: 0.877647, - totalVolume: 7485377052, - high1d: 0.999925, - low1d: 0.999805, - circulatingSupply: 61662495506.43694, - dilutedMarketCap: 61685561207, - marketCapPercentChange1d: 0.11783, - priceChange1d: 0.00002247, - pricePercentChange1h: -0.0020655637951524234, - pricePercentChange1d: 0.002247764341345683, - pricePercentChange7d: -0.006438761910950978, - pricePercentChange14d: 0.007949688389332093, - pricePercentChange30d: 0.014871097408860527, - pricePercentChange200d: -0.024999725392617834, - pricePercentChange1y: -0.09035143373815327, - }, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-2.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-2.ts deleted file mode 100644 index 1cd834932..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-2.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - StringifiedBigInt, - StringifiedNumber, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_1_TRANSACTION_2_DATA: SolanaTransaction = { - blockTime: 1737042188n as UnixTimestamp, - meta: { - computeUnitsConsumed: 28695n, - // eslint-disable-next-line id-denylist - err: null, - fee: 15000n as Lamports, - innerInstructions: [ - { - index: 2, - instructions: [ - { - accounts: [7], - data: '84eT' as Base58EncodedBytes, - programIdIndex: 9, - stackHeight: 2, - }, - { - accounts: [0, 2], - data: '11119os1e9qSs2u7TsThXqkBSRVFxhmYaFKFZ1waB2X7armDmvK3p5GmLdUxYdg3h7QSrL' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: 2, - }, - { - accounts: [2], - data: 'P' as Base58EncodedBytes, - programIdIndex: 9, - stackHeight: 2, - }, - { - accounts: [2, 7], - data: '6XUdiN9B74WsHrJPwmwamQbaUXRoYfHSVCBnsJfParEKM' as Base58EncodedBytes, - programIdIndex: 9, - stackHeight: 2, - }, - ], - }, - ], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]', - 'Program log: Create', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]', - 'Program log: Instruction: GetAccountDataSize', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1595 of 28101 compute units', - 'Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program 11111111111111111111111111111111 invoke [2]', - 'Program 11111111111111111111111111111111 success', - 'Program log: Initialize the associated token account', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]', - 'Program log: Instruction: InitializeImmutableOwner', - 'Program log: Please upgrade to SPL Token 2022 for immutable owner support', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 21488 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]', - 'Program log: Instruction: InitializeAccount3', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4214 of 17604 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 22141 of 35193 compute units', - 'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]', - 'Program log: Instruction: TransferChecked', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6254 of 13052 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - ], - postBalances: [ - 5297910720n, - 2039280n, - 2039280n, - 1n, - 731913600n, - 2783815040n, - 1n, - 68479484100n, - 1009200n, - 934087680n, - ] as Lamports[], - postTokenBalances: [ - { - accountIndex: 1, - mint: asAddress('Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'), - owner: asAddress('DtMUkCoeyzs35B6EpQQxPyyog6TRwXxV1W1Acp8nWBNa'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '500000' as StringifiedBigInt, - decimals: 6, - uiAmount: 0.5, - uiAmountString: '0.5' as StringifiedNumber, - }, - }, - { - accountIndex: 2, - mint: asAddress('Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'), - owner: asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '500000' as StringifiedBigInt, - decimals: 6, - uiAmount: 0.5, - uiAmountString: '0.5' as StringifiedNumber, - }, - }, - ], - preBalances: [ - 5299965000n, - 2039280n, - 0n, - 1n, - 731913600n, - 2783815040n, - 1n, - 68479484100n, - 1009200n, - 934087680n, - ] as Lamports[], - preTokenBalances: [ - { - accountIndex: 1, - mint: asAddress('Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'), - owner: asAddress('DtMUkCoeyzs35B6EpQQxPyyog6TRwXxV1W1Acp8nWBNa'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '1000000' as StringifiedBigInt, - decimals: 6, - uiAmount: 1.0, - uiAmountString: '1' as StringifiedNumber, - }, - }, - ], - rewards: [], - status: { Ok: null }, - }, - slot: 354529585n, - transaction: { - message: { - accountKeys: [ - asAddress('DtMUkCoeyzs35B6EpQQxPyyog6TRwXxV1W1Acp8nWBNa'), - asAddress('4dj1oSSzmpJ2a4biKE6W8ME2bRzwGcjddcVXBuvE4Y4x'), - asAddress('4XRbx7Ut74GPQGNmceU2hcxQUspJoi8rU1Ej4Tj6GX1w'), - asAddress('11111111111111111111111111111111'), - asAddress('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'), - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('ComputeBudget111111111111111111111111111111'), - asAddress('Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr'), - asAddress('SysvarRent111111111111111111111111111111111'), - asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 7, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: '3cvcr9dfxjhH' as Base58EncodedBytes, - programIdIndex: 6, - stackHeight: null, - }, - { - accounts: [], - data: 'JKUyEw' as Base58EncodedBytes, - programIdIndex: 6, - stackHeight: null, - }, - { - accounts: [0, 2, 5, 7, 3, 9, 8], - data: '' as Base58EncodedBytes, - programIdIndex: 4, - stackHeight: null, - }, - { - accounts: [1, 7, 2, 0, 0], - data: 'gX37MVsfGUBn5' as Base58EncodedBytes, - programIdIndex: 9, - stackHeight: null, - }, - ], - recentBlockhash: - 'AzfhwvmnNT4veqCReET7d5edqSpEt7oH9JCYh4yqoB11' as Blockhash, - }, - signatures: ['signature-2'] as Base58EncodedBytes[], - }, - version: 'legacy', -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-3.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-3.ts deleted file mode 100644 index 12fcc9a51..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-3.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - StringifiedBigInt, - StringifiedNumber, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_1_TRANSACTION_3_DATA: SolanaTransaction = { - blockTime: 1736940723n as UnixTimestamp, - meta: { - computeUnitsConsumed: 4644n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [1]', - 'Program log: Instruction: Transfer', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4644 of 200000 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - ], - postBalances: [2783815040n, 2039280n, 2039280n, 934087680n] as Lamports[], - postTokenBalances: [ - { - accountIndex: 1, - mint: asAddress('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'), - owner: asAddress('BXT1K8kzYXWMi6ihg7m9UqiHW4iJbJ69zumELHE9oBLe'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '130000' as StringifiedBigInt, - decimals: 6, - uiAmount: 0.13, - uiAmountString: '0.13' as StringifiedNumber, - }, - }, - { - accountIndex: 2, - mint: asAddress('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'), - owner: asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '7609876' as StringifiedBigInt, - decimals: 6, - uiAmount: 7.609876, - uiAmountString: '7.609876' as StringifiedNumber, - }, - }, - ], - preBalances: [2783820040n, 2039280n, 2039280n, 934087680n] as Lamports[], - preTokenBalances: [ - { - accountIndex: 1, - mint: asAddress('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'), - owner: asAddress('BXT1K8kzYXWMi6ihg7m9UqiHW4iJbJ69zumELHE9oBLe'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '120000' as StringifiedBigInt, - decimals: 6, - uiAmount: 0.12, - uiAmountString: '0.12' as StringifiedNumber, - }, - }, - { - accountIndex: 2, - mint: asAddress('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'), - owner: asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - programId: asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '7619876' as StringifiedBigInt, - decimals: 6, - uiAmount: 7.619876, - uiAmountString: '7.619876' as StringifiedNumber, - }, - }, - ], - rewards: [], - status: { Ok: null }, - }, - slot: 354263676n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('644PJ6UW8e4gQpjKdBVd4MCYasWjSECKjqd2qzdeAJY6'), - asAddress('G23tQHsbQuh3yqUBoyXDn3TwqEbbbUHAHEeUSvJaVRtA'), - asAddress('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 1, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [2, 1, 0], - data: '3GAG5eogvTjV' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - ], - recentBlockhash: - 'AHkrNj8Mk9xH64SzQwHYkg1HRmMZL7ZABgvNGDy56A5p' as Blockhash, - }, - signatures: ['signature-3'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-4.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-4.ts deleted file mode 100644 index e76afac62..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-1/transaction-4.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_1_TRANSACTION_4_DATA: SolanaTransaction = { - blockTime: 1736791224n as UnixTimestamp, - meta: { - computeUnitsConsumed: 300n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program 11111111111111111111111111111111 invoke [1]', - 'Program 11111111111111111111111111111111 success', - ], - postBalances: [2783820040n, 7500935000n, 1n, 1n] as Lamports[], - postTokenBalances: [], - preBalances: [2883825040n, 7400935000n, 1n, 1n] as Lamports[], - preTokenBalances: [], - rewards: [], - status: { Ok: null }, - }, - slot: 353870768n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('FvS1p2dQnhWNrHyuVpJRU5mkYRkSTrubXHs4XrAn3PGo'), - asAddress('11111111111111111111111111111111'), - asAddress('ComputeBudget111111111111111111111111111111'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 2, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'FDJTAf' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - { - accounts: [0, 1], - data: '3Bxs411Dtc7pkFQj' as Base58EncodedBytes, - programIdIndex: 2, - stackHeight: null, - }, - ], - recentBlockhash: - '6ozFDhGMSjqJuDc3HsjWNwejBviFDaF9XWgkLyjQzFx8' as Blockhash, - }, - signatures: ['signature-4'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-1.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-1.ts deleted file mode 100644 index 717ed8b71..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-1.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_2_TRANSACTION_1_DATA: SolanaTransaction = { - blockTime: 1736791224n as UnixTimestamp, - meta: { - computeUnitsConsumed: 300n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program 11111111111111111111111111111111 invoke [1]', - 'Program 11111111111111111111111111111111 success', - ], - postBalances: [2783820040n, 7500935000n, 1n, 1n] as Lamports[], - postTokenBalances: [], - preBalances: [2883825040n, 7400935000n, 1n, 1n] as Lamports[], - preTokenBalances: [], - rewards: [], - status: { Ok: null }, - }, - slot: 353870768n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('FvS1p2dQnhWNrHyuVpJRU5mkYRkSTrubXHs4XrAn3PGo'), - asAddress('11111111111111111111111111111111'), - asAddress('ComputeBudget111111111111111111111111111111'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 2, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'FDJTAf' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - { - accounts: [0, 1], - data: '3Bxs411Dtc7pkFQj' as Base58EncodedBytes, - programIdIndex: 2, - stackHeight: null, - }, - ], - recentBlockhash: - '6ozFDhGMSjqJuDc3HsjWNwejBviFDaF9XWgkLyjQzFx8' as Blockhash, - }, - signatures: ['signature-5'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-2.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-2.ts deleted file mode 100644 index 52a388d34..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-2.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_2_TRANSACTION_2_DATA: SolanaTransaction = { - blockTime: 1736790937n as UnixTimestamp, - meta: { - computeUnitsConsumed: 300n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program 11111111111111111111111111111111 invoke [1]', - 'Program 11111111111111111111111111111111 success', - ], - postBalances: [2883830040n, 7400935000n, 1n, 1n] as Lamports[], - postTokenBalances: [], - preBalances: [2983835040n, 7300935000n, 1n, 1n] as Lamports[], - preTokenBalances: [], - rewards: [], - status: { Ok: null }, - }, - slot: 353870020n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('FvS1p2dQnhWNrHyuVpJRU5mkYRkSTrubXHs4XrAn3PGo'), - asAddress('11111111111111111111111111111111'), - asAddress('ComputeBudget111111111111111111111111111111'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 2, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'FDJTAf' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - { - accounts: [0, 1], - data: '3Bxs411Dtc7pkFQj' as Base58EncodedBytes, - programIdIndex: 2, - stackHeight: null, - }, - ], - recentBlockhash: - 'H3UXVntFNsvkEPxoMpZhN23SXvn8TstXCmexPrt4YcJt' as Blockhash, - }, - signatures: ['signature-6'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-3.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-3.ts deleted file mode 100644 index 84237f977..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-3.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_2_TRANSACTION_3_DATA: SolanaTransaction = { - blockTime: 1736790801n as UnixTimestamp, - meta: { - computeUnitsConsumed: 300n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program 11111111111111111111111111111111 invoke [1]', - 'Program 11111111111111111111111111111111 success', - ], - postBalances: [2983835040n, 7300935000n, 1n, 1n] as Lamports[], - postTokenBalances: [], - preBalances: [3083840040n, 7200935000n, 1n, 1n] as Lamports[], - preTokenBalances: [], - rewards: [], - status: { Ok: null }, - }, - slot: 353869664n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('FvS1p2dQnhWNrHyuVpJRU5mkYRkSTrubXHs4XrAn3PGo'), - asAddress('11111111111111111111111111111111'), - asAddress('ComputeBudget111111111111111111111111111111'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 2, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'FDJTAf' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - { - accounts: [0, 1], - data: '3Bxs411Dtc7pkFQj' as Base58EncodedBytes, - programIdIndex: 2, - stackHeight: null, - }, - ], - recentBlockhash: - '7pD7SyNQAazBGrP7FBHUbcbgXndk31KN7ZstoJDEa7HP' as Blockhash, - }, - signatures: ['signature-7'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-4.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-4.ts deleted file mode 100644 index 985b5a381..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/address-2/transaction-4.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { - Base58EncodedBytes, - Blockhash, - Lamports, - Slot, - UnixTimestamp, -} from '@solana/kit'; -import { address as asAddress } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../../types/solana'; - -export const ADDRESS_2_TRANSACTION_4_DATA: SolanaTransaction = { - blockTime: 1736778620n as UnixTimestamp, - meta: { - computeUnitsConsumed: 300n, - // eslint-disable-next-line id-denylist - err: null, - fee: 5000n as Lamports, - innerInstructions: [], - loadedAddresses: { readonly: [], writable: [] }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program 11111111111111111111111111111111 invoke [1]', - 'Program 11111111111111111111111111111111 success', - ], - postBalances: [3083845040n, 7200935000n, 1n, 1n] as Lamports[], - postTokenBalances: [], - preBalances: [3183850040n, 7100935000n, 1n, 1n] as Lamports[], - preTokenBalances: [], - rewards: [], - status: { Ok: null }, - }, - slot: 353838024n, - transaction: { - message: { - accountKeys: [ - asAddress('BLw3RweJmfbTapJRgnPRvd962YDjFYAnVGd1p5hmZ5tP'), - asAddress('FvS1p2dQnhWNrHyuVpJRU5mkYRkSTrubXHs4XrAn3PGo'), - asAddress('11111111111111111111111111111111'), - asAddress('ComputeBudget111111111111111111111111111111'), - ], - addressTableLookups: [], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 2, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'FDJTAf' as Base58EncodedBytes, - programIdIndex: 3, - stackHeight: null, - }, - { - accounts: [0, 1], - data: '3Bxs411Dtc7pkFQj' as Base58EncodedBytes, - programIdIndex: 2, - stackHeight: null, - }, - ], - recentBlockhash: - '7F3LS15wnw4oXRKWAxjCsqPRqoRyKCMo1TZGVkBn6a4q' as Blockhash, - }, - signatures: ['signature-8'] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap-failed-transaction.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap-failed-transaction.ts deleted file mode 100644 index d0559e2a0..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap-failed-transaction.ts +++ /dev/null @@ -1,427 +0,0 @@ -import type { - Address, - Base58EncodedBytes, - Slot, - StringifiedBigInt, - StringifiedNumber, - TransactionVersion, - UnixTimestamp, -} from '@solana/kit'; -import { address, blockhash, lamports } from '@solana/kit'; - -import type { SolanaTransaction } from '../../../types/solana'; - -/** - * Mainnet - Failed Swap - * Transaction: 58FymkjJUeSFGeEdaUQZbhHP5tdwwvbRR8BfKfuEgfYznqDqsApRBk8LCtiKny9EjQZBNi5NxGvLjR6F3gY6rxn1 - * - * Senders: - * - * Receivers: - */ -export const EXPECTED_SWAP_FAILED_TRANSACTION_DATA: SolanaTransaction = { - blockTime: 1741949141n as UnixTimestamp, - meta: { - computeUnitsConsumed: 104305n, - // eslint-disable-next-line id-denylist - err: { InstructionError: [2, { Custom: 6001 }] }, - fee: lamports(5146n), - innerInstructions: [ - { - index: 2, - instructions: [ - { - accounts: [19, 9, 20, 9, 10, 11, 9, 9, 9, 9, 9, 9, 9, 9, 2, 1, 0], - data: '6BKGqBSufxSksaFn6hyPx7y' as Base58EncodedBytes, - programIdIndex: 21, - stackHeight: 2, - }, - { - accounts: [2, 10, 0], - data: '3asRj1HGdfcj' as Base58EncodedBytes, - programIdIndex: 19, - stackHeight: 3, - }, - { - accounts: [11, 1, 20], - data: '3fc5BGrysRgT' as Base58EncodedBytes, - programIdIndex: 19, - stackHeight: 3, - }, - { - accounts: [8], - data: 'QMqFu4fYGGeUEysFnenhAvR83g86EDDNxzUskfkWKYCBPWe1hqgD6jgKAXr6aYoEQaxoqYMTvWgPVk2AHWGHjdbNiNtoaPfZA4znu6cRUSWSeNvfLkckkHKqHjKCnJkxLXV9QwBLjkhoGQMnnFPa9iPrJDN5T49CBrqKXkkitM11EVM' as Base58EncodedBytes, - programIdIndex: 6, - stackHeight: 2, - }, - { - accounts: [ - 13, 22, 16, 12, 1, 2, 23, 18, 17, 22, 0, 19, 19, 24, 22, 3, 14, - 15, - ], - data: 'PgQWtn8ozixA6EmPobjQWbAuEjTkWPn1D' as Base58EncodedBytes, - programIdIndex: 22, - stackHeight: 2, - }, - { - accounts: [1, 23, 16, 0], - data: 'i6MX5fxEonuqF' as Base58EncodedBytes, - programIdIndex: 19, - stackHeight: 3, - }, - { - accounts: [12, 18, 2, 13], - data: 'gN473h1hKRCjS' as Base58EncodedBytes, - programIdIndex: 19, - stackHeight: 3, - }, - { - accounts: [24], - data: 'yCGxBopjnVNQkNP5usq1PoUkBBY8efS9eeBA1qhuF9ePgc1cABPtx5bsdarGDujyQLmAHAjMZALUwa5hQXdu6qFTrdgpemWGFDEWPuEcyXjyHyJowWY4Z71oiHHU4QHHabd4cMWc2ShGcVxmeHyWSut8xFgjyWAX23EMDqet4ACZeztUBT9Czou2fGeAZCzaXRauaF' as Base58EncodedBytes, - programIdIndex: 22, - stackHeight: 3, - }, - { - accounts: [8], - data: 'QMqFu4fYGGeUEysFnenhAvBobXTzswhLdvQq6s8axxcbKUPRksm2543pJNNNHVd1VKRCxxLHPz7xKMZ6b7SFpnjHpir89pgVG9kwtsnYkobbEF4E1hjvE4LSPQcDFSW3QCZG23k3j82aB5Kc9wgXBdBiYHyMBeemVHsBhKJ5YVSRyaP' as Base58EncodedBytes, - programIdIndex: 6, - stackHeight: 2, - }, - ], - }, - ], - loadedAddresses: { - readonly: [ - address('So11111111111111111111111111111111111111112'), - address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - address('5Q544fKrFoe6tsEbD7SEmxGTJYAKtTVhAW5Q5pge4j1'), - address('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'), - address('LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo'), - address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - address('D1ZN9Wj1fRSUQfCjhvnu1hqDMT7hzjzBBpi12nVniYD6'), - ], - writable: [ - address('3ysyXtPPXnDiipsHovd6aaHdmuS62B3MTWgZBL5TF4Lc'), - address('7j7M3whuNZ2pNK8KWFJLVY5EWiF3P5HvxkUZb9LMiKxf'), - address('8ns4WyfWQCWkNAHDyRPnT6J4yqSKZL6GBWTWoXkwK4Jr'), - address('7kVpsksdgQ6cUaWNsw6xRK5a3S6PJtz9JKcp8k6N4HqG'), - address('8j2dXaFbkzoUaxVtuAadqpNEzQoPJcpuGQquWWaZFp6J'), - address('AkVaJ7h2B8Q5VE2Gq5Un5eiF1B8xTWi1QnvPuCTG9eAZ'), - address('EjU7eN3QmnT5YyXXaW7f7oMcv91yrsoT8EKiDk63NvHc'), - address('GqXYpBYyYa7eeRxSe59HaWtL8LadHxAj5rjUKiskjUht'), - address('H8Fug2JQVfRmkMtc4nhCSondkQN4tLy7TyrQ9wFDenvX'), - ], - }, - logMessages: [ - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program ComputeBudget111111111111111111111111111111 invoke [1]', - 'Program ComputeBudget111111111111111111111111111111 success', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [1]', - 'Program log: Instruction: Route', - 'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 invoke [2]', - 'Program log: ray_log: A4ACvesBAAAAAAAAAAAAAAACAAAAAAAAANqSgOMWAAAA0zl6354AAAA8uE7KlTAAAJxSMDeUAAAA', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]', - 'Program log: Instruction: Transfer', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 123909 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]', - 'Program log: Instruction: Transfer', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 116192 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 consumed 29658 of 140350 compute units', - 'Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 success', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [2]', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 184 of 108962 compute units', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [2]', - 'Program log: Instruction: Swap', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]', - 'Program log: Instruction: TransferChecked', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6147 of 67780 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]', - 'Program log: Instruction: TransferChecked', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 6238 of 58200 compute units', - 'Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo invoke [3]', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 2134 of 48531 compute units', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo consumed 58780 of 103609 compute units', - 'Program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo success', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 invoke [2]', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 184 of 43097 compute units', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 success', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 104005 of 145700 compute units', - 'Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 failed: custom program error: 0x1771', - ], - postBalances: [ - lamports(3255137060n), - lamports(2039280n), - lamports(98308174027n), - lamports(71437440n), - lamports(1n), - lamports(1n), - lamports(1141440n), - lamports(731913600n), - lamports(0n), - lamports(206124800n), - lamports(682356201411n), - lamports(2039280n), - lamports(455539991932n), - lamports(7182720n), - lamports(71437440n), - lamports(71437440n), - lamports(2039280n), - lamports(23385600n), - lamports(999714983180n), - lamports(934087680n), - lamports(22305427381n), - lamports(1141440n), - lamports(1141440n), - lamports(83000835n), - lamports(4000000n), - ], - postTokenBalances: [ - { - accountIndex: 1, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('9az5xpAV8KJ2Q2Jb1ZvBpvfUa5Cj4dZirbgvfPF5XsB8'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '0' as StringifiedBigInt, - decimals: 6, - uiAmount: null, - uiAmountString: '0' as StringifiedNumber, - }, - }, - { - accountIndex: 2, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('9az5xpAV8KJ2Q2Jb1ZvBpvfUa5Cj4dZirbgvfPF5XsB8'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '98306134746' as StringifiedBigInt, - decimals: 9, - uiAmount: 98.306134746, - uiAmountString: '98.306134746' as StringifiedNumber, - }, - }, - { - accountIndex: 10, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '682354162131' as StringifiedBigInt, - decimals: 9, - uiAmount: 682.354162131, - uiAmountString: '682.354162131' as StringifiedNumber, - }, - }, - { - accountIndex: 11, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '53419902416956' as StringifiedBigInt, - decimals: 6, - uiAmount: 53419902.416956, - uiAmountString: '53419902.416956' as StringifiedNumber, - }, - }, - { - accountIndex: 12, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('8j2dXaFbkzoUaxVtuAadqpNEzQoPJcpuGQquWWaZFp6J'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '455537952652' as StringifiedBigInt, - decimals: 9, - uiAmount: 455.537952652, - uiAmountString: '455.537952652' as StringifiedNumber, - }, - }, - { - accountIndex: 16, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('8j2dXaFbkzoUaxVtuAadqpNEzQoPJcpuGQquWWaZFp6J'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '39795030203226' as StringifiedBigInt, - decimals: 6, - uiAmount: 39795030.203226, - uiAmountString: '39795030.203226' as StringifiedNumber, - }, - }, - ], - preBalances: [ - lamports(3255142206n), - lamports(2039280n), - lamports(98308174027n), - lamports(71437440n), - lamports(1n), - lamports(1n), - lamports(1141440n), - lamports(731913600n), - lamports(0n), - lamports(206124800n), - lamports(682356201411n), - lamports(2039280n), - lamports(455539991932n), - lamports(7182720n), - lamports(71437440n), - lamports(71437440n), - lamports(2039280n), - lamports(23385600n), - lamports(999714983180n), - lamports(934087680n), - lamports(22305427381n), - lamports(1141440n), - lamports(1141440n), - lamports(83000835n), - lamports(4000000n), - ], - preTokenBalances: [ - { - accountIndex: 1, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('9az5xpAV8KJ2Q2Jb1ZvBpvfUa5Cj4dZirbgvfPF5XsB8'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '0' as StringifiedBigInt, - decimals: 6, - uiAmount: null, - uiAmountString: '0' as StringifiedNumber, - }, - }, - { - accountIndex: 2, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('9az5xpAV8KJ2Q2Jb1ZvBpvfUa5Cj4dZirbgvfPF5XsB8'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '98306134746' as StringifiedBigInt, - decimals: 9, - uiAmount: 98.306134746, - uiAmountString: '98.306134746' as StringifiedNumber, - }, - }, - { - accountIndex: 10, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '682354162131' as StringifiedBigInt, - decimals: 9, - uiAmount: 682.354162131, - uiAmountString: '682.354162131' as StringifiedNumber, - }, - }, - { - accountIndex: 11, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '53419902416956' as StringifiedBigInt, - decimals: 6, - uiAmount: 53419902.416956, - uiAmountString: '53419902.416956' as StringifiedNumber, - }, - }, - { - accountIndex: 12, - mint: address('So11111111111111111111111111111111111111112'), - owner: address('8j2dXaFbkzoUaxVtuAadqpNEzQoPJcpuGQquWWaZFp6J'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '455537952652' as StringifiedBigInt, - decimals: 9, - uiAmount: 455.53792652, - uiAmountString: '455.53792652' as StringifiedNumber, - }, - }, - { - accountIndex: 16, - mint: address('9m3nh7YDoF1WSYpNxCjKVU8D1MrXsWRic4HqRaTdcTYB'), - owner: address('8j2dXaFbkzoUaxVtuAadqpNEzQoPJcpuGQquWWaZFp6J'), - programId: address('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), - uiTokenAmount: { - amount: '39795030203226' as StringifiedBigInt, - decimals: 6, - uiAmount: 39795030.203226, - uiAmountString: '39795030.203226' as StringifiedNumber, - }, - }, - ], - rewards: [], - status: { Err: { InstructionError: [2, { Custom: 6001 }] } }, - }, - slot: 326686187n, - transaction: { - message: { - accountKeys: [ - address('9az5xpAV8KJ2Q2Jb1ZvBpvfUa5Cj4dZirbgvfPF5XsB8'), - address('5AZA8P8dN5VMvkBeqbP5rJx2pWBr5cUo4FRYXY8LMh5i'), - address('9fuGSiFvpkWQutKZAPW8TZDdyjHaCWyZpjGeeenxHz9c'), - address('E2bvANTdbG1dSbFuZNWbiHQrrQkntA8gX1GkBzn44HXv'), - address('11111111111111111111111111111111'), - address('ComputeBudget111111111111111111111111111111'), - address('JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'), - address('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'), - address('D8cy77BBepLMngZx6ZukaTff5hCt1HrWyKk3Hnd9oitf'), - ] as Address[], - addressTableLookups: [ - { - accountKey: address('9RqhCVZij68nidFH9UydNHvuz4t9unLKi6zvkeokcpiN'), - readableIndexes: [3, 2, 57, 54], - writableIndexes: [133, 130, 129], - }, - { - accountKey: address('4MxCjViJkeZE7TGk3MX5UQA8ti6NViLKnN17yRfmfVbN'), - readableIndexes: [145, 137, 141], - writableIndexes: [140, 139, 135, 142, 144, 136], - }, - ], - header: { - numReadonlySignedAccounts: 0, - numReadonlyUnsignedAccounts: 5, - numRequiredSignatures: 1, - }, - instructions: [ - { - accounts: [], - data: 'G919uy' as Base58EncodedBytes, - programIdIndex: 5, - stackHeight: null, - }, - { - accounts: [], - data: '3tGNFMqHiozw' as Base58EncodedBytes, - programIdIndex: 5, - stackHeight: null, - }, - { - accounts: [ - 19, 0, 2, 2, 6, 18, 6, 8, 6, 21, 19, 9, 20, 9, 10, 11, 9, 9, 9, 9, - 9, 9, 9, 9, 2, 1, 0, 22, 13, 22, 16, 12, 1, 2, 23, 18, 17, 22, 0, - 19, 19, 24, 22, 3, 14, 15, 6, - ], - data: '3aafXU8vKpJ2E1MADMA3LWJLQVy2M6eNFTCXwReN36ZiGt3qHu4m1R' as Base58EncodedBytes, - programIdIndex: 6, - stackHeight: null, - }, - ], - recentBlockhash: blockhash( - 'D2CkGEKruGkek11gFQqoRptnXWeTu4CF2h7V4A8PgW37', - ), - }, - signatures: [ - '58FymkjJUeSFGeEdaUQZbhHP5tdwwvbRR8BfKfuEgfYznqDqsApRBk8LCtiKny9EjQZBNi5NxGvLjR6F3gY6rxn1', - ] as Base58EncodedBytes[], - }, - version: 0, -}; diff --git a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap.ts b/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap.ts deleted file mode 100644 index 4b606b9a6..000000000 --- a/packages/solana-wallet-snap/src/core/test/mocks/transactions-data/swap.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const MOCK_VALID_SWAP_TRANSACTION = - 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAJGb90BPMeQxbCdwSbyC2lv/FG3wE/28MLN5GTUYRikvRDkOL72EsPrSrrKZF33sPiMFwhF786GU/O6Np6ngUZdtMjqo7S3idbRg4oDnEPLya1vPuQf89zrLobei3jVynGDWZ/y1OFUjaxGSYPYSYxL38DI5jSwryQ6RMhtMx+2NckC3UgzyHnGpR6VJ/vU2WjD5DjwsvxD8xvVd650NGXdFPfFDaFT76ZIor8z07o9ErtPu3r9GAOUB8XlcFOWqe0GbVg2nwBF4l0ttMopHOlyK+KfsPeVOMeRyLf74oiXgZpxRtTT3ZTysIJA79qg1dQT8Wj+GTHAMGTFDs3n24716MkP6jxjf4+aNNNmPpXtUfvmxMIAY7riPwpO4SFhpVj0XEzfZHfK+df8cHOiMH18Ck85+5FYvbuPgRfoH+q0xHdtI87EL3RhZBcQk4vzHrzbkAkE7DwtVHIo8xFIraXVSCz5bV/G3n7wr0zknZABvXObI/TVq0OyaCULUJJ+4wRxu/Ab3BgJLxDIWyhxeghwuw3hQHQsddhorJLkDoHnFEe4QbQHUu/XbvkfR9I9VbH7APhtv1iOmKh8B4KL0eLA2qtZCdRkuyZUpEk9d4ij6b7goSocmdwGNZYeO9YHrIxsBTGnIKVMZIov5wu5RjgHDxo2EPhQveTgkkiYamH6wF3KD0N0oI1T+8K47DiJ9N82JyiZvsX3fj3y3zO++Tr3FUGp9UXGMd0yShWY5hpHV62i164o5tLbVxzVVshAAAAAEkKjsh0MV4N5BZqrYoVn502rqoySfW65Wb3zHaXL7aVAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAACMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4WQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABHnVW/IxwG7udMVuzmgVB/2xst6j9I5RArHNola8E4+agAv/TIc2iJbCD8FAc+vxy1qjdf6B/k29yCuk37deeLQ/+if11/ZKdMCbHylYed5LCas238ndUUsyGqezjOXo5K2meepn3qneL1SbuOijeqwwK3lRyEWrdscvyuCXNhUFEAIREgkAcW2PgwH1yAATAAUCkaMFABMACQP7LQIAAAAAABQGAAEAJRUmAQEWSSYXAAIDBAEnJRYWGBYoHSgeHwMFKycgKBcmJikoBgcIFighKCIjBQkrKiQoFyYmKSgKCwwWKBkoGhsJBCUqHCgXJiYpKA0ODxYswSCbM0HWnIECAwAAACZkAAEmZAECJmQCA0BCDwAAAAAAFBlnfQEAAAAyAAADL2c++byrysDHksR/hJ46kiqc8he+GbowBx6uV7CZWyME29zZ4AbaAJwBA0K46+6aQbZXU5t0wJLWiAcN0mjgFssRN0gyDYS+vpMa2ATMb3LOAXEKxf5RTUsVyCew1xJHNL+DxGC6MsqRsYXEZ/tnyaBY0QRcmV1bAA=='; diff --git a/packages/solana-wallet-snap/src/core/types/form.ts b/packages/solana-wallet-snap/src/core/types/form.ts deleted file mode 100644 index b47743983..000000000 --- a/packages/solana-wallet-snap/src/core/types/form.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { LocalizedMessage } from '../utils/i18n'; -import type { FormFieldError } from './error'; - -export type FormState = Record< - FormNames, - string | number | boolean | null ->; - -export type FieldValidationFunction = (value: string) => FormFieldError; -export type ValidationFunction = ( - message: LocalizedMessage, - value?: any, -) => FieldValidationFunction; diff --git a/packages/solana-wallet-snap/src/core/utils/concurrency.test.ts b/packages/solana-wallet-snap/src/core/utils/concurrency.test.ts deleted file mode 100644 index 41fce73d4..000000000 --- a/packages/solana-wallet-snap/src/core/utils/concurrency.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { CancellablePromise } from './concurrency'; -import { - CancellationError, - withCancellable, - withoutConcurrency, -} from './concurrency'; - -describe('concurrency', () => { - describe('withCancellable', () => { - it('resolves normally when not cancelled', async () => { - const mockFn = jest.fn().mockResolvedValue('result'); - const cancellable = withCancellable(mockFn); - - const result = await cancellable(); - expect(result).toBe('result'); - expect(mockFn).toHaveBeenCalledTimes(1); - }); - - it('rejects with CancellationError when cancelled', async () => { - const mockFn = jest.fn().mockImplementation(async () => { - return new Promise((resolve) => { - setTimeout(() => resolve('Hello'), 1000); - }); - }); - const cancellable = withCancellable(mockFn); - - const promise = cancellable(); - promise.cancel(); - - await expect(promise).rejects.toThrow(CancellationError); - expect(mockFn).toHaveBeenCalledTimes(1); - }); - - it('does not resolve after cancellation even if original promise resolves', async () => { - // Create a manually controlled promise that we can resolve explicitly - let resolveOriginal: (value: string) => void; - const originalPromise = new Promise((resolve) => { - resolveOriginal = resolve; - }); - - const mockFn = jest.fn().mockReturnValue(originalPromise); - const cancellable = withCancellable(mockFn); - - // Start the cancellable operation - const promise = cancellable(); - - // Set up a way to check if the promise resolves - let wasResolved = false; - promise - .then(() => { - wasResolved = true; - }) - .catch(() => { - // We expect it to be rejected with CancellationError - }); - - // Cancel it - promise.cancel(); - - // Now resolve the original promise - resolveOriginal!('result'); - - // Wait a bit to ensure any potential resolution would have happened - await new Promise((resolve) => setTimeout(resolve, 50)); - - // Verify the promise was rejected, not resolved - expect(wasResolved).toBe(false); - await expect(promise).rejects.toThrow(CancellationError); - expect(mockFn).toHaveBeenCalledTimes(1); - }); - - it('supports multiple concurrent cancellable operations', async () => { - const delays = [100, 50, 150]; - const mockFn = jest - .fn() - .mockImplementation( - async (delay: number) => - new Promise((resolve) => - setTimeout(() => resolve(`finished ${delay}`), delay), - ), - ); - - const cancellable = withCancellable(mockFn); - - // Create cancellable promises directly - - const promises = delays.map((delay) => - cancellable(delay), - ) as CancellablePromise[]; - - // Cancel first and last operations - promises[0]!.cancel(); - promises[2]!.cancel(); - - const results = await Promise.allSettled(promises); - - expect(results[0]).toMatchObject({ - status: 'rejected', - reason: expect.any(CancellationError), - }); - expect(results[1]).toMatchObject({ - status: 'fulfilled', - value: 'finished 50', - }); - expect(results[2]).toMatchObject({ - status: 'rejected', - reason: expect.any(CancellationError), - }); - }); - - it('supports cancellation of the original function', async () => { - // Create a mock function that simulates a long-running operation - // and properly handles the AbortSignal - const mockFn = jest - .fn() - .mockImplementation(async (_param: string, signal?: AbortSignal) => { - // Check if already aborted - if (signal?.aborted) { - throw new CancellationError(); - } - - return new Promise((resolve, reject) => { - // Set up a timeout to simulate work - const timeout = setTimeout(() => resolve('completed'), 1000); - - // Set up cancellation handler - signal?.addEventListener('abort', () => { - clearTimeout(timeout); - reject(new CancellationError('Operation aborted')); - }); - }); - }); - - const cancellable = withCancellable(mockFn); - - // Start the operation - const promise = cancellable('test-param'); - - // Cancel it - promise.cancel(); - - // Verify it was cancelled - await expect(promise).rejects.toThrow(CancellationError); - - // Verify the mock function was called with the signal - expect(mockFn).toHaveBeenCalledTimes(1); - expect(mockFn.mock.calls[0][0]).toBe('test-param'); - expect(mockFn.mock.calls[0][1]).toBeInstanceOf(AbortSignal); - - // Verify the signal was aborted - const passedSignal = mockFn.mock.calls[0][1] as AbortSignal; - expect(passedSignal.aborted).toBe(true); - }); - }); - - describe('withoutConcurrency', () => { - it('executes a task normally when there is no previous task', async () => { - // Create a mock function that returns a regular promise - const mockFn = jest.fn().mockResolvedValue('result'); - - // Wrap with withoutConcurrency - const nonConcurrentFn = withoutConcurrency(mockFn); - - // Execute the function - const result = await nonConcurrentFn(); - - // Verify the result - expect(result).toBe('result'); - expect(mockFn).toHaveBeenCalledTimes(1); - }); - - it('cancels previous task when a new task is started', async () => { - // Create a manually controlled promise - let resolveFirst: (value: string) => void; - const firstPromise = new Promise((resolve) => { - resolveFirst = resolve; - }); - - // Create a mock function that returns our controlled promise for the first call - // and a regular resolved promise for the second call - const mockFn = jest - .fn() - .mockReturnValueOnce(firstPromise) - .mockResolvedValueOnce('second result'); - - // Create non-concurrent version from the promise-returning function - const nonConcurrentFn = withoutConcurrency(mockFn); - - // Start the first task (but don't resolve it yet) - const firstTask = nonConcurrentFn(); - - // Set up a way to check if the first task was cancelled - let firstWasCancelled = false; - firstTask.catch((error) => { - if (error instanceof CancellationError) { - firstWasCancelled = true; - } - }); - - // Start a second task before the first one completes - const secondResult = await nonConcurrentFn(); - - // Now resolve the first task - resolveFirst!('first result'); - - // Wait a bit to ensure any potential resolution would have happened - await new Promise((resolve) => setTimeout(resolve, 50)); - - // Verify the first task was cancelled and the second task completed - expect(firstWasCancelled).toBe(true); - expect(secondResult).toBe('second result'); - expect(mockFn).toHaveBeenCalledTimes(2); - }); - - it('allows a new task to start after previous task completes', async () => { - // Create a mock function - const mockFn = jest - .fn() - .mockResolvedValueOnce('first result') - .mockResolvedValueOnce('second result'); - - // Create non-concurrent version - const nonConcurrentFn = withoutConcurrency(mockFn); - - // Execute the first task and wait for it to complete - const firstResult = await nonConcurrentFn(); - - // Execute the second task - const secondResult = await nonConcurrentFn(); - - // Verify both tasks completed successfully - expect(firstResult).toBe('first result'); - expect(secondResult).toBe('second result'); - expect(mockFn).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/concurrency.ts b/packages/solana-wallet-snap/src/core/utils/concurrency.ts deleted file mode 100644 index ae32bf639..000000000 --- a/packages/solana-wallet-snap/src/core/utils/concurrency.ts +++ /dev/null @@ -1,114 +0,0 @@ -import logger from './logger'; - -export type CancellablePromise = Promise & { - cancel: () => void; -}; - -export class CancellationError extends Error { - constructor(message = 'Operation cancelled') { - super(message); - this.name = 'CancellationError'; - } -} - -/** - * Wraps a promise-returning function to make it cancellable. - * - * WARNING: Only the decorated function will be cancelled, not the original function. - * However, the AbortSignal is passed down to the original function, so it can use the signal to cancel its own operations. - * - * @example - * const processData = async (id: string) => { - * await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work - * return { id, result: `Processed ${id}` }; - * }; - * - * const cancellableProcessData = withCancellable(processData); - * cancellableProcessData('123'); - * cancellableProcessData.cancel(); // This will cancel the previous process for '123' - * @example - * const fetchData = async (url: string, signal?: AbortSignal) => { - * const response = await fetch(url, { signal }); - * return response.json(); - * }; - * - * const cancellableFetch = withCancellable(fetchData); - * const promise = cancellableFetch('https://api.example.com/data'); - * promise.cancel(); // This will abort the fetch request - * @param fn - The function to make cancellable. - * @returns A cancellable promise. - */ -export const withCancellable = ( - fn: (...args: any[]) => Promise, -): ((...args: any[]) => CancellablePromise) => { - return (...args) => { - const abortController = new AbortController(); - - // Pass the AbortController's signal to the original function - // We'll add the signal as the last argument if it's not already provided - const argsWithSignal = [...args]; - if (!argsWithSignal.some((arg) => arg instanceof AbortSignal)) { - argsWithSignal.push(abortController.signal); - } - - const promise = Promise.race([ - fn(...argsWithSignal), - new Promise((_, reject) => { - abortController.signal.addEventListener('abort', () => { - reject(new CancellationError()); - }); - }), - ]) as CancellablePromise; - - promise.cancel = () => abortController.abort(); - return promise; - }; -}; - -/** - * Wraps a promise-returning function to prevent concurrent executions. - * Automatically converts the promise to a cancellable promise internally. - * - * @example - * const processData = async (id: string) => { - * await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work - * return { id, result: `Processed ${id}` }; - * }; - * - * const nonConcurrentProcessData = withoutConcurrency(processData); - * - * // Only the latest call will complete, previous calls are cancelled - * nonConcurrentProcessData('123'); - * nonConcurrentProcessData('456'); // This will cancel the previous process for '123' - * @param fn - The function to wrap. - * @returns A cancellable promise. - */ -export function withoutConcurrency( - fn: (...args: any[]) => Promise, -): (...args: any[]) => Promise { - // Convert the function to return a cancellable promise - const cancellableFn = withCancellable(fn); - - let currentTask: CancellablePromise | null = null; - - return async (...args: any[]) => { - if (currentTask) { - if (typeof currentTask.cancel === 'function') { - const message = 'Cancelling previous task'; - logger.warn(message); - currentTask.cancel(); - } - } - - const newTask: CancellablePromise = cancellableFn(...args); - currentTask = newTask; - - try { - return await newTask; - } finally { - if (currentTask === newTask) { - currentTask = null; - } - } - }; -} diff --git a/packages/solana-wallet-snap/src/core/utils/diffArrays.test.ts b/packages/solana-wallet-snap/src/core/utils/diffArrays.test.ts deleted file mode 100644 index 000f5a924..000000000 --- a/packages/solana-wallet-snap/src/core/utils/diffArrays.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { diffArrays } from './diffArrays'; - -describe('diffArrays', () => { - it('returns added and deleted elements when there are differences', () => { - const array1 = [1, 2, 3]; - const array2 = [2, 3, 4]; - const result = diffArrays(array1, array2); - - expect(result).toStrictEqual({ - added: [4], - deleted: [1], - hasDiff: true, - }); - }); - - it('returns empty arrays when there are no differences', () => { - const array1 = [1, 2, 3]; - const array2 = [1, 2, 3]; - const result = diffArrays(array1, array2); - - expect(result).toStrictEqual({ - added: [], - deleted: [], - hasDiff: false, - }); - }); - - it('returns all elements as added when the first array is empty', () => { - const array1: number[] = []; - const array2 = [1, 2, 3]; - const result = diffArrays(array1, array2); - - expect(result).toStrictEqual({ - added: [1, 2, 3], - deleted: [], - hasDiff: true, - }); - }); - - it('returns all elements as deleted when the second array is empty', () => { - const array1 = [1, 2, 3]; - const array2: number[] = []; - const result = diffArrays(array1, array2); - - expect(result).toStrictEqual({ - added: [], - deleted: [1, 2, 3], - hasDiff: true, - }); - }); - - it('handles arrays with different types', () => { - const array1 = ['a', 'b', 'c']; - const array2 = ['b', 'c', 'd']; - const result = diffArrays(array1, array2); - - expect(result).toStrictEqual({ - added: ['d'], - deleted: ['a'], - hasDiff: true, - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/diffArrays.ts b/packages/solana-wallet-snap/src/core/utils/diffArrays.ts deleted file mode 100644 index b317a663b..000000000 --- a/packages/solana-wallet-snap/src/core/utils/diffArrays.ts +++ /dev/null @@ -1,26 +0,0 @@ -type DiffResult = { - added: Type[]; - deleted: Type[]; - hasDiff: boolean; -}; - -/** - * Computes the difference between two arrays. - * - * @param array1 - The first array. - * @param array2 - The second array. - * @returns An object containing the added and deleted elements. - */ -export function diffArrays( - array1: Type[], - array2: Type[], -): DiffResult { - const added = array2.filter((item) => !array1.includes(item)); - const deleted = array1.filter((item) => !array2.includes(item)); - - return { - hasDiff: added.length > 0 || deleted.length > 0, - added, - deleted, - }; -} diff --git a/packages/solana-wallet-snap/src/core/utils/diffObjects.test.ts b/packages/solana-wallet-snap/src/core/utils/diffObjects.test.ts deleted file mode 100644 index ba28dedb3..000000000 --- a/packages/solana-wallet-snap/src/core/utils/diffObjects.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { diffObjects } from './diffObjects'; - -describe('diffObjects', () => { - it('detects added properties', () => { - const object1 = { a: 1 }; - const object2 = { a: 1, b: 2 }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: { b: 2 }, - deleted: {}, - changed: {}, - hasDiff: true, - }); - }); - - it('detects deleted properties', () => { - const object1 = { a: 1, b: 2 }; - const object2 = { a: 1 }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: {}, - deleted: { b: 2 }, - changed: {}, - hasDiff: true, - }); - }); - - it('detects nested added properties', () => { - const object1 = { a: 1, b: { c: 3 } }; - const object2 = { a: 1, b: { c: 3, d: 4 } }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: { 'b.d': 4 }, - deleted: {}, - changed: {}, - hasDiff: true, - }); - }); - - it('detects nested deleted properties', () => { - const object1 = { a: 1, b: { c: 3, d: 4 } }; - const object2 = { a: 1, b: { c: 3 } }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: {}, - deleted: { 'b.d': 4 }, - changed: {}, - hasDiff: true, - }); - }); - - it('detects changed properties', () => { - const object1 = { a: 1, b: 2 }; - const object2 = { a: 1, b: 3 }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: {}, - deleted: {}, - changed: { b: 3 }, - hasDiff: true, - }); - }); - - it('detects no differences', () => { - const object1 = { a: 1, b: 2 }; - const object2 = { a: 1, b: 2 }; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: {}, - deleted: {}, - changed: {}, - hasDiff: false, - }); - }); - - it('handles empty objects', () => { - const object1 = {}; - const object2 = {}; - const result = diffObjects(object1, object2); - expect(result).toStrictEqual({ - added: {}, - deleted: {}, - changed: {}, - hasDiff: false, - }); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/diffObjects.ts b/packages/solana-wallet-snap/src/core/utils/diffObjects.ts deleted file mode 100644 index 900cd6e8c..000000000 --- a/packages/solana-wallet-snap/src/core/utils/diffObjects.ts +++ /dev/null @@ -1,66 +0,0 @@ -type DiffResult = { - added: Record; - deleted: Record; - changed: Record; - hasDiff: boolean; -}; - -/** - * Computes the differences between two objects. - * - * @param object1 - The first object to compare. - * @param object2 - The second object to compare. - * @returns The differences between the two objects. - */ -export function diffObjects( - object1: Record, - object2: Record, -): DiffResult { - const diffs: DiffResult = { - added: {}, - deleted: {}, - changed: {}, - hasDiff: false, - }; - - const findDiffs = ( - o1: Record, - o2: Record, - path = '', - ) => { - for (const key in o1) { - if (Object.prototype.hasOwnProperty.call(o1, key)) { - const newPath = path ? `${path}.${key}` : key; - if (!Object.prototype.hasOwnProperty.call(o2, key)) { - diffs.deleted[newPath] = o1[key]; - diffs.hasDiff = true; - } else if ( - typeof o1[key] === 'object' && - o1[key] !== null && - typeof o2[key] === 'object' && - o2[key] !== null - ) { - findDiffs(o1[key], o2[key], newPath); - } else if (o1[key] !== o2[key]) { - const topKey = newPath.split('.')[0]; - diffs.changed[topKey ?? key] = object2[topKey ?? key]; - diffs.hasDiff = true; - } - } - } - - for (const key in o2) { - if ( - Object.prototype.hasOwnProperty.call(o2, key) && - !Object.prototype.hasOwnProperty.call(o1, key) - ) { - const newPath = path ? `${path}.${key}` : key; - diffs.added[newPath] = o2[key]; - diffs.hasDiff = true; - } - } - }; - - findDiffs(object1, object2); - return diffs; -} diff --git a/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.test.ts b/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.test.ts deleted file mode 100644 index af1c9db28..000000000 --- a/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import BigNumber from 'bignumber.js'; - -import { formatFiatBalance } from './formatFiatBalance'; - -describe('formatFiatBalance', () => { - it('should format number inputs to 2 decimal places', () => { - expect(formatFiatBalance(123.456)).toBe('123.46'); - expect(formatFiatBalance(123.45)).toBe('123.45'); - expect(formatFiatBalance(123)).toBe('123.00'); - }); - - it('should format string inputs to 2 decimal places', () => { - expect(formatFiatBalance('123.456')).toBe('123.46'); - expect(formatFiatBalance('123.45')).toBe('123.45'); - expect(formatFiatBalance('123')).toBe('123.00'); - }); - - it('should format BigNumber inputs to 2 decimal places', () => { - expect(formatFiatBalance(new BigNumber('123.456'))).toBe('123.46'); - expect(formatFiatBalance(new BigNumber('123.45'))).toBe('123.45'); - expect(formatFiatBalance(new BigNumber('123'))).toBe('123.00'); - }); - - it('should handle zero values', () => { - expect(formatFiatBalance(0)).toBe('0.00'); - expect(formatFiatBalance('0')).toBe('0.00'); - expect(formatFiatBalance(new BigNumber(0))).toBe('0.00'); - }); - - it('should handle negative values', () => { - expect(formatFiatBalance(-123.456)).toBe('-123.46'); - expect(formatFiatBalance('-123.456')).toBe('-123.46'); - expect(formatFiatBalance(new BigNumber(-123.456))).toBe('-123.46'); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.ts b/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.ts deleted file mode 100644 index cccbc9b56..000000000 --- a/packages/solana-wallet-snap/src/core/utils/formatFiatBalance.ts +++ /dev/null @@ -1,14 +0,0 @@ -import BigNumber from 'bignumber.js'; - -/** - * Formats a number to 2 decimal places. - * - * @param amount - The amount of money. - * @returns The formatted string. - */ -export function formatFiatBalance(amount: number | string | BigNumber) { - const bigAmount = new BigNumber(amount); - const amountNumber = bigAmount.toNumber().toFixed(2); - - return amountNumber; -} diff --git a/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.test.ts b/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.test.ts deleted file mode 100644 index f73668359..000000000 --- a/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { getAccountIdFromAddress } from './getAccountIdFromAddress'; - -describe('getAccountIdFromAddress', () => { - it('should return the account ID if the address is found', () => { - const accounts = [ - { id: '1', address: '123' }, - { id: '2', address: '456' }, - ]; - const address = '123'; - - const result = getAccountIdFromAddress(accounts as any, address); - - expect(result).toBe('1'); - }); - - it('should return undefined if the address is not found', () => { - const accounts = [ - { id: '1', address: '123' }, - { id: '2', address: '456' }, - ]; - const address = '789'; - - const result = getAccountIdFromAddress(accounts as any, address); - - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.ts b/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.ts deleted file mode 100644 index 3b9bfe746..000000000 --- a/packages/solana-wallet-snap/src/core/utils/getAccountIdFromAddress.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { KeyringAccount } from '@metamask/keyring-api'; - -/** - * Finds the account ID from the address. - * - * @param accounts - The accounts to search through. - * @param address - The address to find the account ID for. - * @returns The account ID if found, otherwise undefined. - */ -export function getAccountIdFromAddress( - accounts: KeyringAccount[], - address: string, -): string | undefined { - return accounts.find((account) => account.address === address)?.id; -} diff --git a/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.test.ts b/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.test.ts deleted file mode 100644 index b866f40e6..000000000 --- a/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Network } from '../constants/solana'; -import { getClusterFromScope } from './getClusterFromScope'; - -describe('getClusterFromScope', () => { - it('returns "Mainnet" for mainnet network', () => { - const result = getClusterFromScope(Network.Mainnet); - expect(result).toBe('Mainnet'); - }); - - it('returns "Devnet" for devnet network', () => { - const result = getClusterFromScope(Network.Devnet); - expect(result).toBe('Devnet'); - }); - - it('returns "Testnet" for testnet network', () => { - const result = getClusterFromScope(Network.Testnet); - expect(result).toBe('Testnet'); - }); - - it('returns undefined for unknown network value', () => { - const result = getClusterFromScope('invalid:network' as unknown as Network); - expect(result).toBeUndefined(); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.ts b/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.ts deleted file mode 100644 index 79fb018cc..000000000 --- a/packages/solana-wallet-snap/src/core/utils/getClusterFromScope.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Network } from '../constants/solana'; - -/** - * Returns the cluster name from the given CAIP-2 scope. - * - * @param lookedUpScope - The CAIP-2 scope to look up. - * @returns The cluster name or undefined if the scope is not found. - */ -export function getClusterFromScope(lookedUpScope: Network) { - for (const [scope, value] of Object.entries(Network)) { - if (value === lookedUpScope) { - return scope as keyof typeof Network; - } - } - - return undefined; -} diff --git a/packages/solana-wallet-snap/src/core/utils/isFiat.test.ts b/packages/solana-wallet-snap/src/core/utils/isFiat.test.ts deleted file mode 100644 index 1e74f8e92..000000000 --- a/packages/solana-wallet-snap/src/core/utils/isFiat.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { CaipAssetType } from '@metamask/keyring-api'; - -import { isFiat } from './isFiat'; - -describe('isFiat', () => { - it('should return true for valid fiat CAIP-19 asset IDs', () => { - expect(isFiat('swift:0/iso4217:USD')).toBe(true); - expect(isFiat('swift:0/iso4217:EUR')).toBe(true); - expect(isFiat('swift:0/iso4217:GBP')).toBe(true); - }); - - it('should return false for non-fiat CAIP-19 asset IDs', () => { - expect(isFiat('eip155:1/erc20:0x123')).toBe(false); - expect(isFiat('bip122:000000000019d6689c085ae165831e93/slip44:1')).toBe( - false, - ); - expect(isFiat('cosmos:cosmoshub-4/slip44:118')).toBe(false); - }); - - it('should return false for invalid or malformed asset IDs', () => { - expect(isFiat('' as CaipAssetType)).toBe(false); - expect(isFiat('swift:0' as CaipAssetType)).toBe(false); - expect(isFiat('iso4217:USD' as CaipAssetType)).toBe(false); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/isFiat.ts b/packages/solana-wallet-snap/src/core/utils/isFiat.ts deleted file mode 100644 index d245907e2..000000000 --- a/packages/solana-wallet-snap/src/core/utils/isFiat.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { CaipAssetType } from '@metamask/keyring-api'; - -/** - * Checks if a CAIP-19 asset ID represents a fiat currency. - * - * @param caipAssetId - The CAIP-19 asset ID to check. - * @returns `true` if the CAIP-19 asset ID represents a fiat currency, `false` otherwise. - */ -export function isFiat(caipAssetId: CaipAssetType): boolean { - return caipAssetId.includes('swift:0/iso4217:'); -} diff --git a/packages/solana-wallet-snap/src/core/utils/toTokenUnit.test.ts b/packages/solana-wallet-snap/src/core/utils/toTokenUnit.test.ts deleted file mode 100644 index 7c860ca3e..000000000 --- a/packages/solana-wallet-snap/src/core/utils/toTokenUnit.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import BigNumber from 'bignumber.js'; - -import { toTokenUnits } from './toTokenUnit'; - -describe('toTokenUnits', () => { - it('converts string amounts correctly', () => { - expect(toTokenUnits('1', 6)).toBe(1000000n); - expect(toTokenUnits('0.1', 6)).toBe(100000n); - expect(toTokenUnits('0.000001', 6)).toBe(1n); - }); - - it('converts number amounts correctly', () => { - expect(toTokenUnits(1, 6)).toBe(1000000n); - expect(toTokenUnits(0.1, 6)).toBe(100000n); - }); - - it('converts BigNumber amounts correctly', () => { - expect(toTokenUnits(new BigNumber('1'), 6)).toBe(1000000n); - expect(toTokenUnits(new BigNumber('0.1'), 6)).toBe(100000n); - }); - - it('converts bigint amounts correctly', () => { - expect(toTokenUnits(1n, 6)).toBe(1000000n); - }); - - it('handles zero correctly', () => { - expect(toTokenUnits('0', 6)).toBe(0n); - expect(toTokenUnits(0, 6)).toBe(0n); - expect(toTokenUnits(new BigNumber(0), 6)).toBe(0n); - expect(toTokenUnits(0n, 6)).toBe(0n); - }); - - it('throws error for negative amounts', () => { - expect(() => toTokenUnits('-1', 6)).toThrow( - 'Token amount cannot be negative', - ); - expect(() => toTokenUnits(-1, 6)).toThrow( - 'Token amount cannot be negative', - ); - expect(() => toTokenUnits(new BigNumber(-1), 6)).toThrow( - 'Token amount cannot be negative', - ); - }); - - it('returns zero for too many decimal places', () => { - expect(toTokenUnits('0.0000001', 6)).toBe(0n); - expect(toTokenUnits(0.0000001, 6)).toBe(0n); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/toTokenUnit.ts b/packages/solana-wallet-snap/src/core/utils/toTokenUnit.ts deleted file mode 100644 index 0684248c5..000000000 --- a/packages/solana-wallet-snap/src/core/utils/toTokenUnit.ts +++ /dev/null @@ -1,24 +0,0 @@ -import BigNumber from 'bignumber.js'; - -/** - * Converts a human-readable token amount to raw token units. - * - * @param amount - The amount in token (e.g., "0.1"). - * @param decimals - The number of decimals the token has (e.g., 6 for USDC). - * @returns The amount in raw units. - * @throws If the amount is negative or would result in an underflow. - */ -export function toTokenUnits( - amount: string | number | bigint | BigNumber, - decimals: number, -): bigint { - const bn = new BigNumber(amount.toString()); - - if (bn.isNegative()) { - throw new Error('Token amount cannot be negative'); - } - - const result = bn.times(10 ** decimals).integerValue(BigNumber.ROUND_DOWN); - - return BigInt(result.toString()); -} diff --git a/packages/solana-wallet-snap/src/core/utils/truncateAddress.test.ts b/packages/solana-wallet-snap/src/core/utils/truncateAddress.test.ts deleted file mode 100644 index a4b70abbc..000000000 --- a/packages/solana-wallet-snap/src/core/utils/truncateAddress.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { truncateAddress } from './truncateAddress'; - -describe('truncateAddress', () => { - it('truncates a long address correctly', () => { - const address = '1234567890abcdef'; - const truncated = truncateAddress(address); - expect(truncated).toBe('123456...cdef'); - }); - - it('handles empty addresses gracefully', () => { - const address = ''; - const truncated = truncateAddress(address); - expect(truncated).toBe(''); - }); - - it('handles short addresses', () => { - const address = '123456'; - const truncated = truncateAddress(address); - expect(truncated).toBe(''); - }); - - it('handles addresses with more than 10 characters', () => { - const address = '12345678900'; - const truncated = truncateAddress(address); - expect(truncated).toBe('123456...8900'); - }); -}); diff --git a/packages/solana-wallet-snap/src/core/utils/truncateAddress.ts b/packages/solana-wallet-snap/src/core/utils/truncateAddress.ts deleted file mode 100644 index 088058da3..000000000 --- a/packages/solana-wallet-snap/src/core/utils/truncateAddress.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Truncates an address to show the first 6 and last 4 characters, separated by ellipses. - * - * @param address - The address to truncate. - * @returns The truncated address. - */ -export function truncateAddress(address: string): string { - if (!address) { - return ''; - } - - if (address.length <= 10) { - return ''; - } - - return `${address.slice(0, 6)}...${address.slice(-4)}`; -} diff --git a/packages/solana-wallet-snap/src/snapContext.ts b/packages/solana-wallet-snap/src/snapContext.ts index 6dfc9d683..0e8085914 100644 --- a/packages/solana-wallet-snap/src/snapContext.ts +++ b/packages/solana-wallet-snap/src/snapContext.ts @@ -1,8 +1,4 @@ -import type { Serializable } from '@metamask/snap-networks-utils'; - -import type { ICache } from './core/caching/ICache'; import { InMemoryCache } from './core/caching/InMemoryCache'; -import { StateCache } from './core/caching/StateCache'; import { NftApiClient } from './core/clients/nft-api/NftApiClient'; import { PriceApiClient } from './core/clients/price-api/PriceApiClient'; import { SecurityAlertsApiClient } from './core/clients/security-alerts-api/SecurityAlertsApiClient'; @@ -39,7 +35,6 @@ import { ConfigProvider } from './core/services/config'; import { ConfirmationHandler } from './core/services/confirmation/ConfirmationHandler'; import { SolanaConnection } from './core/services/connection/SolanaConnection'; import { NameResolutionService } from './core/services/name-resolution/NameResolutionService'; -import { NftService } from './core/services/nft/NftService'; import type { IStateManager } from './core/services/state/IStateManager'; import type { UnencryptedStateValue } from './core/services/state/State'; import { DEFAULT_UNENCRYPTED_STATE, State } from './core/services/state/State'; @@ -67,8 +62,6 @@ export type SnapExecutionContext = { transactionScanService: TransactionScanService; analyticsService: AnalyticsService; confirmationHandler: ConfirmationHandler; - cache: ICache; - nftService: NftService; clientRequestHandler: ClientRequestHandler; webSocketConnectionService: WebSocketConnectionService; subscriptionService: SubscriptionService; @@ -88,7 +81,6 @@ const state = new State(eventEmitter, { defaultState: DEFAULT_UNENCRYPTED_STATE, }); -const stateCache = new StateCache(state, logger); const inMemoryCache = new InMemoryCache(noOpLogger); const analyticsService = new AnalyticsService(logger); @@ -233,8 +225,6 @@ const keyring = new SolanaKeyring({ keyringAccountMonitor, }); -const nftService = new NftService(connection, logger); - const sendService = new SendService( connection, keyring, @@ -266,7 +256,6 @@ const snapContext: SnapExecutionContext = { keyring, priceApiClient, state, - cache: stateCache, /* Services */ assetsService, signer, @@ -277,7 +266,6 @@ const snapContext: SnapExecutionContext = { transactionScanService, analyticsService, confirmationHandler, - nftService, clientRequestHandler, webSocketConnectionService, subscriptionService, @@ -289,30 +277,16 @@ const snapContext: SnapExecutionContext = { }; export { - accountsService, accountsSynchronizer, analyticsService, - assetsService, clientRequestHandler, - configProvider, - confirmationHandler, connection, eventEmitter, keyring, nameResolutionService, - nftService, priceApiClient, - sendSolBuilder, - sendSplTokenBuilder, - signer, state, - subscriptionRepository, - subscriptionService, - tokenApiClient, - tokenHelper, transactionScanService, - transactionsService, - walletService, webSocketConnectionService, };