diff --git a/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.test.ts b/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.test.ts new file mode 100644 index 0000000000..8a5d063dd6 --- /dev/null +++ b/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.test.ts @@ -0,0 +1,378 @@ +import { Messenger } from '@metamask/base-controller'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; +import { KeyringClient } from '@metamask/keyring-snap-client'; +import { useFakeTimers } from 'sinon'; + +import { MultiChainAssetsRatesController } from '.'; +import { + type AllowedActions, + type AllowedEvents, +} from './MultichainAssetsRatesController'; + +// A fake non‑EVM account (with Snap metadata) that meets the controller’s criteria. +const fakeNonEvmAccount: InternalAccount = { + id: 'account1', + type: 'solana:data-account', + address: '0x123', + metadata: { + name: 'Test Account', + // @ts-expect-error-next-line + snap: { id: 'test-snap', enabled: true }, + }, + scopes: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], + options: {}, + methods: [], +}; + +// A fake EVM account (which should be filtered out). +const fakeEvmAccount: InternalAccount = { + id: 'account2', + type: 'eip155:eoa', + address: '0x456', + // @ts-expect-error-next-line + metadata: { name: 'EVM Account' }, + scopes: [], + options: {}, + methods: [], +}; + +const fakeEvmAccount2: InternalAccount = { + id: 'account3', + type: 'bip122:p2wpkh', + address: '0x789', + metadata: { + name: 'EVM Account', + // @ts-expect-error-next-line + snap: { id: 'test-snap', enabled: true }, + }, + scopes: [], + options: {}, + methods: [], +}; + +const fakeEvmAccountWithoutMetadata: InternalAccount = { + id: 'account4', + type: 'bip122:p2wpkh', + address: '0x789', + metadata: { + name: 'EVM Account', + importTime: 0, + keyring: { type: 'bip122' }, + }, + scopes: [], + options: {}, + methods: [], +}; + +// A fake conversion rates response returned by the SnapController. +const fakeAccountRates = { + conversionRates: { + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + 'swift:0/iso4217:USD': { + rate: '202.11', + conversionTime: 1738539923277, + }, + }, + }, +}; + +const setupController = ({ + config, + accountsAssets = [fakeNonEvmAccount, fakeEvmAccount, fakeEvmAccount2], +}: { + config?: Partial< + ConstructorParameters[0] + >; + accountsAssets?: InternalAccount[]; +} = {}) => { + const messenger = new Messenger(); + + messenger.registerActionHandler( + 'MultichainAssetsController:getState', + () => ({ + accountsAssets: { + account1: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'], + account2: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'], + account3: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'], + }, + assetsMetadata: { + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + name: 'Solana', + symbol: 'SOL', + fungible: true, + iconUrl: 'https://example.com/solana.png', + units: [{ symbol: 'SOL', name: 'Solana', decimals: 9 }], + }, + }, + }), + ); + + messenger.registerActionHandler( + 'AccountsController:listMultichainAccounts', + () => accountsAssets, + ); + + messenger.registerActionHandler('CurrencyRateController:getState', () => ({ + currencyRates: {}, + currentCurrency: 'USD', + })); + + const multiChainAssetsRatesControllerMessenger = messenger.getRestricted({ + name: 'MultiChainAssetsRatesController', + allowedActions: [ + 'AccountsController:listMultichainAccounts', + 'SnapController:handleRequest', + 'CurrencyRateController:getState', + 'MultichainAssetsController:getState', + ], + allowedEvents: [ + 'AccountsController:accountAdded', + 'KeyringController:lock', + 'KeyringController:unlock', + 'CurrencyRateController:stateChange', + 'MultichainAssetsController:stateChange', + ], + }); + + return { + controller: new MultiChainAssetsRatesController({ + messenger: multiChainAssetsRatesControllerMessenger, + ...config, + }), + messenger, + }; +}; + +describe('MultiChainAssetsRatesController', () => { + let clock: sinon.SinonFakeTimers; + + const mockedDate = 1705760550000; + + beforeEach(() => { + clock = useFakeTimers(); + jest.spyOn(Date, 'now').mockReturnValue(mockedDate); + }); + + afterEach(() => { + clock.restore(); + jest.restoreAllMocks(); + }); + + it('initializes with an empty conversionRates state', () => { + const { controller } = setupController(); + expect(controller.state).toStrictEqual({ conversionRates: {} }); + }); + + it('updates conversion rates for a valid non-EVM account', async () => { + const { controller, messenger } = setupController(); + + // Stub KeyringClient.listAccountAssets so that the controller “discovers” one asset. + jest + .spyOn(KeyringClient.prototype, 'listAccountAssets') + .mockResolvedValue([ + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + ]); + + // Override the SnapController:handleRequest handler to return our fake conversion rates. + const snapHandler = jest.fn().mockResolvedValue(fakeAccountRates); + messenger.registerActionHandler( + 'SnapController:handleRequest', + snapHandler, + ); + + // Call updateAssetsRates for the valid non-EVM account. + await controller.updateAssetsRates(); + + // Check that the Snap request was made with the expected parameters. + expect(snapHandler).toHaveBeenCalledWith( + expect.objectContaining({ + handler: 'onAssetsConversion', + origin: 'metamask', + request: { + jsonrpc: '2.0', + method: 'onAssetsConversion', + params: { + conversions: [ + { + from: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + to: 'swift:0/iso4217:USD', + }, + ], + }, + }, + snapId: 'test-snap', + }), + ); + + // The controller state should now contain the conversion rates returned. + expect(controller.state.conversionRates).toStrictEqual( + // fakeAccountRates.conversionRates, + { + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + rate: '202.11', + conversionTime: 1738539923277, + currency: 'swift:0/iso4217:USD', + }, + }, + ); + }); + + it('does not update conversion rates if the controller is not active', async () => { + const { controller, messenger } = setupController(); + + // Simulate a keyring lock event to set the controller as inactive. + messenger.publish('KeyringController:lock'); + // Override SnapController:handleRequest and stub listAccountAssets. + const snapHandler = jest.fn().mockResolvedValue(fakeAccountRates); + messenger.registerActionHandler( + 'SnapController:handleRequest', + snapHandler, + ); + jest + .spyOn(KeyringClient.prototype, 'listAccountAssets') + .mockResolvedValue([ + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + ]); + + await controller.updateAssetsRates(); + // Since the controller is locked, no update should occur. + expect(controller.state.conversionRates).toStrictEqual({}); + expect(snapHandler).not.toHaveBeenCalled(); + }); + + it('resumes update tokens rates when the keyring is unlocked', async () => { + const { controller, messenger } = setupController(); + messenger.publish('KeyringController:lock'); + // Override SnapController:handleRequest and stub listAccountAssets. + const snapHandler = jest.fn().mockResolvedValue(fakeAccountRates); + messenger.registerActionHandler( + 'SnapController:handleRequest', + snapHandler, + ); + jest + .spyOn(KeyringClient.prototype, 'listAccountAssets') + .mockResolvedValue([ + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + ]); + await controller.updateAssetsRates(); + expect(controller.isActive).toBe(false); + + messenger.publish('KeyringController:unlock'); + await controller.updateAssetsRates(); + + expect(controller.isActive).toBe(true); + }); + + it('calls updateTokensRates when _executePoll is invoked', async () => { + const { controller, messenger } = setupController(); + + jest + .spyOn(KeyringClient.prototype, 'listAccountAssets') + .mockResolvedValue([ + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501', + ]); + + messenger.registerActionHandler( + 'SnapController:handleRequest', + async () => ({ + conversionRates: { + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + 'swift:0/iso4217:USD': { + rate: '202.11', + conversionTime: 1738539923277, + }, + }, + }, + }), + ); + + // Spy on updateAssetsRates. + const updateSpy = jest.spyOn(controller, 'updateAssetsRates'); + await controller._executePoll(); + expect(updateSpy).toHaveBeenCalled(); + }); + + it('calls updateTokensRates when an multichain assets state is updated', async () => { + const { controller, messenger } = setupController(); + + // Spy on updateTokensRates. + const updateSpy = jest + .spyOn(controller, 'updateAssetsRates') + .mockResolvedValue(); + + // Publish a selectedAccountChange event. + // @ts-expect-error-next-line + messenger.publish('MultichainAssetsController:stateChange', { + accountsAssets: { + account3: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501'], + }, + }); + // Wait for the asynchronous subscriber to run. + await Promise.resolve(); + expect(updateSpy).toHaveBeenCalled(); + }); + + it('handles partial or empty Snap responses gracefully', async () => { + const { controller, messenger } = setupController(); + + messenger.registerActionHandler('SnapController:handleRequest', () => { + return Promise.resolve({ + conversionRates: { + // Only returning a rate for one asset + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + 'swift:0/iso4217:USD': { + rate: '250.50', + conversionTime: 1738539923277, + }, + }, + }, + }); + }); + + await controller.updateAssetsRates(); + + expect(controller.state.conversionRates).toMatchObject({ + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501': { + rate: '250.50', + conversionTime: 1738539923277, + }, + }); + }); + + it('skips all accounts that lack Snap metadata or are EVM', async () => { + const { controller, messenger } = setupController({ + accountsAssets: [fakeEvmAccountWithoutMetadata], + }); + + const snapSpy = jest.fn().mockResolvedValue({ conversionRates: {} }); + messenger.registerActionHandler('SnapController:handleRequest', snapSpy); + + await controller.updateAssetsRates(); + + expect(snapSpy).not.toHaveBeenCalled(); + expect(controller.state.conversionRates).toStrictEqual({}); + }); + + it('updates state when currency is updated', async () => { + const { controller, messenger } = setupController(); + + const snapHandler = jest.fn().mockResolvedValue(fakeAccountRates); + messenger.registerActionHandler( + 'SnapController:handleRequest', + snapHandler, + ); + + const updateSpy = jest.spyOn(controller, 'updateAssetsRates'); + + messenger.publish( + 'CurrencyRateController:stateChange', + { + currentCurrency: 'EUR', + currencyRates: {}, + }, + [], + ); + + expect(updateSpy).toHaveBeenCalled(); + }); +}); diff --git a/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts b/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts new file mode 100644 index 0000000000..ebf7b85587 --- /dev/null +++ b/packages/assets-controllers/src/MultichainAssetsRatesController/MultichainAssetsRatesController.ts @@ -0,0 +1,440 @@ +import type { + AccountsControllerListMultichainAccountsAction, + AccountsControllerAccountAddedEvent, +} from '@metamask/accounts-controller'; +import type { + RestrictedMessenger, + ControllerStateChangeEvent, + ControllerGetStateAction, +} from '@metamask/base-controller'; +import { type CaipAssetType, isEvmAccountType } from '@metamask/keyring-api'; +import type { + KeyringControllerLockEvent, + KeyringControllerUnlockEvent, +} from '@metamask/keyring-controller'; +import type { InternalAccount } from '@metamask/keyring-internal-api'; +import { StaticIntervalPollingController } from '@metamask/polling-controller'; +import type { HandleSnapRequest } from '@metamask/snaps-controllers'; +import type { + SnapId, + AssetConversion, + OnAssetsConversionArguments, + OnAssetsConversionResponse, +} from '@metamask/snaps-sdk'; +import { HandlerType } from '@metamask/snaps-utils'; +import { Mutex } from 'async-mutex'; +import type { Draft } from 'immer'; + +import { MAP_CAIP_CURRENCIES } from './constant'; +import type { + CurrencyRateState, + CurrencyRateStateChange, + GetCurrencyRateState, +} from '../CurrencyRateController'; +import type { + MultichainAssetsControllerGetStateAction, + MultichainAssetsControllerState, + MultichainAssetsControllerStateChangeEvent, +} from '../MultichainAssetsController'; + +/** + * The name of the MultiChainAssetsRatesController. + */ +const controllerName = 'MultiChainAssetsRatesController'; + +/** + * State used by the MultiChainAssetsRatesController to cache token conversion rates. + */ +export type MultichainAssetsRatesControllerState = { + conversionRates: Record; +}; + +/** + * Returns the state of the MultiChainAssetsRatesController. + */ +export type MultichainAssetsRatesControllerGetStateAction = + ControllerGetStateAction< + typeof controllerName, + MultichainAssetsRatesControllerState + >; + +/** + * Action to update the rates of all supported tokens. + */ +export type MultichainAssetsRatesControllerUpdateRatesAction = { + type: `${typeof controllerName}:updateAssetsRates`; + handler: MultiChainAssetsRatesController['updateAssetsRates']; +}; + +/** + * Constructs the default {@link MultichainAssetsRatesController} state. This allows + * consumers to provide a partial state object when initializing the controller + * and also helps in constructing complete state objects for this controller in + * tests. + * + * @returns The default {@link MultichainAssetsRatesController} state. + */ +export function getDefaultMultichainAssetsRatesControllerState(): MultichainAssetsRatesControllerState { + return { conversionRates: {} }; +} + +/** + * Event emitted when the state of the MultiChainAssetsRatesController changes. + */ +export type MultichainAssetsRatesControllerStateChange = + ControllerStateChangeEvent< + typeof controllerName, + MultichainAssetsRatesControllerState + >; + +/** + * Actions exposed by the MultiChainAssetsRatesController. + */ +export type MultichainAssetsRatesControllerActions = + | MultichainAssetsRatesControllerGetStateAction + | MultichainAssetsRatesControllerUpdateRatesAction; + +/** + * Events emitted by MultiChainAssetsRatesController. + */ +export type MultichainAssetsRatesControllerEvents = + MultichainAssetsRatesControllerStateChange; + +/** + * Actions that this controller is allowed to call. + */ +export type AllowedActions = + | HandleSnapRequest + | AccountsControllerListMultichainAccountsAction + | GetCurrencyRateState + | MultichainAssetsControllerGetStateAction; +/** + * Events that this controller is allowed to subscribe to. + */ +export type AllowedEvents = + | KeyringControllerLockEvent + | KeyringControllerUnlockEvent + | AccountsControllerAccountAddedEvent + | CurrencyRateStateChange + | MultichainAssetsControllerStateChangeEvent; + +/** + * Messenger type for the MultiChainAssetsRatesController. + */ +export type MultichainAssetsRatesControllerMessenger = RestrictedMessenger< + typeof controllerName, + MultichainAssetsRatesControllerActions | AllowedActions, + MultichainAssetsRatesControllerEvents | AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] +>; + +/** + * The input for starting polling in MultiChainAssetsRatesController. + */ +export type MultiChainAssetsRatesPollingInput = { + accountId: string; +}; + +const metadata = { + conversionRates: { persist: true, anonymous: true }, +}; + +/** + * Controller that manages multichain token conversion rates. + * + * This controller polls for token conversion rates and updates its state. + */ +export class MultiChainAssetsRatesController extends StaticIntervalPollingController()< + typeof controllerName, + MultichainAssetsRatesControllerState, + MultichainAssetsRatesControllerMessenger +> { + readonly #mutex = new Mutex(); + + #currentCurrency: CurrencyRateState['currentCurrency']; + + #accountsAssets: MultichainAssetsControllerState['accountsAssets']; + + #isUnlocked = true; + + /** + * Creates an instance of MultiChainAssetsRatesController. + * + * @param options - Constructor options. + * @param options.interval - The polling interval in milliseconds. + * @param options.state - The initial state. + * @param options.messenger - A reference to the messaging system. + */ + constructor({ + interval = 18000, + state = {}, + messenger, + }: { + interval?: number; + state?: Partial; + messenger: MultichainAssetsRatesControllerMessenger; + }) { + super({ + name: controllerName, + messenger, + state: { + ...getDefaultMultichainAssetsRatesControllerState(), + ...state, + }, + metadata, + }); + + this.setIntervalLength(interval); + + // Subscribe to keyring lock/unlock events. + this.messagingSystem.subscribe('KeyringController:lock', () => { + this.#isUnlocked = false; + }); + this.messagingSystem.subscribe('KeyringController:unlock', () => { + this.#isUnlocked = true; + }); + + ({ accountsAssets: this.#accountsAssets } = this.messagingSystem.call( + 'MultichainAssetsController:getState', + )); + + ({ currentCurrency: this.#currentCurrency } = this.messagingSystem.call( + 'CurrencyRateController:getState', + )); + + this.messagingSystem.subscribe( + 'CurrencyRateController:stateChange', + async (currencyRatesState: CurrencyRateState) => { + this.#currentCurrency = currencyRatesState.currentCurrency; + await this.updateAssetsRates(); + }, + ); + + this.messagingSystem.subscribe( + 'MultichainAssetsController:stateChange', + async (multiChainAssetsState: MultichainAssetsControllerState) => { + this.#accountsAssets = multiChainAssetsState.accountsAssets; + await this.updateAssetsRates(); + }, + ); + } + + /** + * Executes a poll by updating token conversion rates for the current account. + * + * @returns A promise that resolves when the polling completes. + */ + async _executePoll(): Promise { + await this.updateAssetsRates(); + } + + /** + * Determines whether the controller is active. + * + * @returns True if the keyring is unlocked; otherwise, false. + */ + get isActive(): boolean { + return this.#isUnlocked; + } + + /** + * Checks if an account is a non-EVM account with a Snap. + * + * @param account - The account to check. + * @returns True if the account is non-EVM and has Snap metadata; otherwise, false. + */ + #isNonEvmAccount(account: InternalAccount): boolean { + return ( + !isEvmAccountType(account.type) && account.metadata.snap !== undefined + ); + } + + /** + * Retrieves all multichain accounts from the AccountsController. + * + * @returns An array of internal accounts. + */ + #listMultichainAccounts(): InternalAccount[] { + return this.messagingSystem.call( + 'AccountsController:listMultichainAccounts', + ); + } + + /** + * Filters and returns non-EVM accounts that should have balances. + * + * @returns An array of non-EVM internal accounts. + */ + #listAccounts(): InternalAccount[] { + const accounts = this.#listMultichainAccounts(); + return accounts.filter((account) => this.#isNonEvmAccount(account)); + } + + /** + * Updates token conversion rates for each non-EVM account. + * + * @returns A promise that resolves when the rates are updated. + */ + async updateAssetsRates(): Promise { + const releaseLock = await this.#mutex.acquire(); + + return (async () => { + if (!this.isActive) { + return; + } + const accounts = this.#listAccounts(); + + for (const account of accounts) { + const assets = this.#getAssetsForAccount(account.id); + + // Build the conversions array + const conversions = this.#buildConversions(assets); + + // Retrieve rates from Snap + const accountRates = await this.#handleSnapRequest({ + snapId: account?.metadata.snap?.id as SnapId, + handler: HandlerType.OnAssetsConversion, + params: conversions, + }); + + // Flatten nested rates if needed + const flattenedRates = this.#flattenRates(accountRates); + + // Build the updatedRates object for these assets + const updatedRates = this.#buildUpdatedRates(assets, flattenedRates); + // Apply these updated rates to controller state + this.#applyUpdatedRates(updatedRates); + } + })().finally(() => { + releaseLock(); + }); + } + + /** + * Returns the array of CAIP-19 assets for the given account ID. + * If none are found, returns an empty array. + * + * @param accountId - The account ID to get the assets for. + * @returns An array of CAIP-19 assets. + */ + #getAssetsForAccount(accountId: string): CaipAssetType[] { + return this.#accountsAssets?.[accountId] ?? []; + } + + /** + * Builds a conversions array (from each asset → the current currency). + * + * @param assets - The assets to build the conversions for. + * @returns A conversions array. + */ + #buildConversions(assets: CaipAssetType[]): OnAssetsConversionArguments { + const currency = + MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? MAP_CAIP_CURRENCIES.usd; + return { + conversions: assets.map((asset) => ({ + from: asset, + to: currency, + })), + }; + } + + /** + * Flattens any nested structure in the conversion rates returned by Snap. + * + * @param assetsConversionResponse - The conversion rates to flatten. + * @returns A flattened rates object. + */ + #flattenRates( + assetsConversionResponse: OnAssetsConversionResponse, + ): Record { + const { conversionRates } = assetsConversionResponse; + + return Object.fromEntries( + Object.entries(conversionRates).map(([asset, nestedObj]) => { + // e.g., nestedObj might look like: { "swift:0/iso4217:EUR": { rate, conversionTime } } + const singleValue = Object.values(nestedObj)[0]; + return [asset, singleValue]; + }), + ); + } + + /** + * Builds a rates object that covers all given assets, ensuring that + * any asset not returned by Snap is set to null for both `rate` and `conversionTime`. + * + * @param assets - The assets to build the rates for. + * @param flattenedRates - The rates to merge. + * @returns A rates object that covers all given assets. + */ + #buildUpdatedRates( + assets: CaipAssetType[], + flattenedRates: Record, + ): Record { + const updatedRates: Record< + CaipAssetType, + AssetConversion & { currency: CaipAssetType } + > = {}; + + for (const asset of assets) { + if (flattenedRates[asset]) { + updatedRates[asset] = { + ...(flattenedRates[asset] as AssetConversion), + currency: + MAP_CAIP_CURRENCIES[this.#currentCurrency] ?? + MAP_CAIP_CURRENCIES.usd, + }; + } + } + return updatedRates; + } + + /** + * Merges the new rates into the controller’s state. + * + * @param updatedRates - The new rates to merge. + */ + #applyUpdatedRates( + updatedRates: Record< + string, + { rate: string | null; conversionTime: number | null } + >, + ): void { + this.update((state: Draft) => { + state.conversionRates = { + ...state.conversionRates, + ...updatedRates, + }; + }); + } + + /** + * Forwards a Snap request to the SnapController. + * + * @param args - The request parameters. + * @param args.snapId - The ID of the Snap. + * @param args.handler - The handler type. + * @param args.params - The asset conversions. + * @returns A promise that resolves with the account rates. + */ + async #handleSnapRequest({ + snapId, + handler, + params, + }: { + snapId: SnapId; + handler: HandlerType; + params: OnAssetsConversionArguments; + }): Promise { + return this.messagingSystem.call('SnapController:handleRequest', { + snapId, + origin: 'metamask', + handler, + request: { + jsonrpc: '2.0', + method: handler, + params, + }, + }) as Promise; + } +} diff --git a/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts b/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts new file mode 100644 index 0000000000..2fef0e8155 --- /dev/null +++ b/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts @@ -0,0 +1,92 @@ +import type { CaipAssetType } from '@metamask/utils'; + +/** + * Maps each SUPPORTED_CURRENCIES entry to its CAIP-19 (or CAIP-like) identifier. + * For fiat, we mimic the old “swift:0/iso4217:XYZ” style. + */ +export const MAP_CAIP_CURRENCIES: { + [key: string]: CaipAssetType; +} = { + // ======================== + // Native crypto assets + // ======================== + btc: 'bip122:000000000019d6689c085ae165831e93/slip44:0', + eth: 'eip155:1/slip44:60', + ltc: 'bip122:12a765e31ffd4059bada1e25190f6e98/slip44:2', + + // Bitcoin Cash + bch: 'bip122:000000000000000000651ef99cb9fcbe/slip44:145', + + // Binance Coin + bnb: 'cosmos:Binance-Chain-Tigris/slip44:714', + + // EOS mainnet (chainId = aca376f2...) + eos: 'eos:aca376f2/slip44:194', + + // XRP mainnet + xrp: 'xrpl:mainnet/slip44:144', + + // Stellar Lumens mainnet + xlm: 'stellar:pubnet/slip44:148', + + // Chainlink (ERC20 on Ethereum mainnet) + link: 'eip155:1/erc20:0x514910771af9Ca656af840dff83E8264EcF986CA', + + // Polkadot (chainId = 91b171bb158e2d3848fa23a9f1c25182) + dot: 'polkadot:91b171bb158e2d3848fa23a9f1c25182/slip44:354', + + // Yearn.finance (ERC20 on Ethereum mainnet) + yfi: 'eip155:1/erc20:0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e', + + // ======================== + // Fiat currencies + // ======================== + usd: 'swift:0/iso4217:USD', + aed: 'swift:0/iso4217:AED', + ars: 'swift:0/iso4217:ARS', + aud: 'swift:0/iso4217:AUD', + bdt: 'swift:0/iso4217:BDT', + bhd: 'swift:0/iso4217:BHD', + bmd: 'swift:0/iso4217:BMD', + brl: 'swift:0/iso4217:BRL', + cad: 'swift:0/iso4217:CAD', + chf: 'swift:0/iso4217:CHF', + clp: 'swift:0/iso4217:CLP', + cny: 'swift:0/iso4217:CNY', + czk: 'swift:0/iso4217:CZK', + dkk: 'swift:0/iso4217:DKK', + eur: 'swift:0/iso4217:EUR', + gbp: 'swift:0/iso4217:GBP', + hkd: 'swift:0/iso4217:HKD', + huf: 'swift:0/iso4217:HUF', + idr: 'swift:0/iso4217:IDR', + ils: 'swift:0/iso4217:ILS', + inr: 'swift:0/iso4217:INR', + jpy: 'swift:0/iso4217:JPY', + krw: 'swift:0/iso4217:KRW', + kwd: 'swift:0/iso4217:KWD', + lkr: 'swift:0/iso4217:LKR', + mmk: 'swift:0/iso4217:MMK', + mxn: 'swift:0/iso4217:MXN', + myr: 'swift:0/iso4217:MYR', + ngn: 'swift:0/iso4217:NGN', + nok: 'swift:0/iso4217:NOK', + nzd: 'swift:0/iso4217:NZD', + php: 'swift:0/iso4217:PHP', + pkr: 'swift:0/iso4217:PKR', + pln: 'swift:0/iso4217:PLN', + rub: 'swift:0/iso4217:RUB', + sar: 'swift:0/iso4217:SAR', + sek: 'swift:0/iso4217:SEK', + sgd: 'swift:0/iso4217:SGD', + thb: 'swift:0/iso4217:THB', + try: 'swift:0/iso4217:TRY', + twd: 'swift:0/iso4217:TWD', + uah: 'swift:0/iso4217:UAH', + vef: 'swift:0/iso4217:VEF', + vnd: 'swift:0/iso4217:VND', + zar: 'swift:0/iso4217:ZAR', + xdr: 'swift:0/iso4217:XDR', + xag: 'swift:0/iso4217:XAG', + xau: 'swift:0/iso4217:XAU', +}; diff --git a/packages/assets-controllers/src/MultichainAssetsRatesController/index.ts b/packages/assets-controllers/src/MultichainAssetsRatesController/index.ts new file mode 100644 index 0000000000..c145b3d21c --- /dev/null +++ b/packages/assets-controllers/src/MultichainAssetsRatesController/index.ts @@ -0,0 +1,13 @@ +export type { + MultichainAssetsRatesControllerState, + MultichainAssetsRatesControllerActions, + MultichainAssetsRatesControllerEvents, + MultichainAssetsRatesControllerGetStateAction, + MultichainAssetsRatesControllerStateChange, + MultichainAssetsRatesControllerMessenger, +} from './MultichainAssetsRatesController'; + +export { + MultiChainAssetsRatesController, + getDefaultMultichainAssetsRatesControllerState, +} from './MultichainAssetsRatesController'; diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 46a689f9f7..97518b56ee 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -171,3 +171,17 @@ export type { MultichainAssetsControllerEvents, MultichainAssetsControllerMessenger, } from './MultichainAssetsController'; + +export { + MultiChainAssetsRatesController, + getDefaultMultichainAssetsRatesControllerState, +} from './MultichainAssetsRatesController'; + +export type { + MultichainAssetsRatesControllerState, + MultichainAssetsRatesControllerActions, + MultichainAssetsRatesControllerEvents, + MultichainAssetsRatesControllerGetStateAction, + MultichainAssetsRatesControllerStateChange, + MultichainAssetsRatesControllerMessenger, +} from './MultichainAssetsRatesController';