Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/snap-networks-utils/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Export namespace-agnostic `MessengerCaller` / `CanCall` types for Snap Core messenger endowments ([#138](https://github.com/MetaMask/internal-snaps/pull/138))

### Changed

- Type `AssetsProviderMessenger` and `RemoteFeatureFlagsProviderMessenger` as `MessengerCaller`s so a Snap Core `getMessenger` result (Snap namespace + action superset) is assignable without casts ([#138](https://github.com/MetaMask/internal-snaps/pull/138))

## [1.0.0]

### Added
Expand Down
9 changes: 7 additions & 2 deletions packages/snap-networks-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ snapLogger.info('account synced');

### Core AssetsController reads

Wire the Snap messenger endowment, then pass it to `AssetsProvider`:
Wire the Snap messenger endowment, then pass it to `AssetsProvider`. Provider
messenger types are namespace-agnostic callers, so the Snap Core messenger (which
uses the Snap's own namespace and may expose additional actions) is assignable
without casts:

```typescript
import type { Messenger } from '@metamask/messenger';
Expand All @@ -48,7 +51,9 @@ type CoreMessengerActions =
| AssetsControllerGetAccountAssetsByIDsAction
| AssetsControllerGetAccountAssetsByScopeAction;

const messenger = getMessenger<Messenger<string, CoreMessengerActions>>();
type CoreMessengerConstraint = Messenger<'ExampleSnap', CoreMessengerActions>;

const messenger = getMessenger<CoreMessengerConstraint>();
const assetsProvider = new AssetsProvider({ messenger });

const accountId: AccountId = '550e8400-e29b-41d4-a716-446655440000';
Expand Down
1 change: 1 addition & 0 deletions packages/snap-networks-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export {
RemoteFeatureFlagsProvider,
type RemoteFeatureFlagsProviderMessenger,
} from './providers/remote-feature-flags/RemoteFeatureFlagsProvider';
export type { CanCall, MessengerCaller } from './types/messenger-caller';
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type {
AssetsControllerGetAccountAssetsByScopeAction,
Caip19AssetId,
} from '@metamask/assets-controller';
import type { Messenger } from '@metamask/messenger';
import { AsyncMessenger } from '@metamask/snaps-sdk';
import type { CaipChainId } from '@metamask/utils';

import type { MessengerCaller } from '../../types/messenger-caller';

/**
* Namespace for the {@link AssetsProvider} messenger.
*/
Expand All @@ -23,11 +23,14 @@ export type AssetsProviderAllowedActions =
| AssetsControllerGetAccountAssetsByScopeAction;

/**
* Messenger restricted to actions consumed by {@link AssetsProvider}.
* Messenger caller for actions consumed by {@link AssetsProvider}.
*
* Namespace-agnostic so a Snap Core `getMessenger` endowment (which uses the
* Snap's own namespace and may expose additional actions) is assignable without
* casts.
*/
export type AssetsProviderMessenger = AsyncMessenger<
Messenger<typeof ASSETS_PROVIDER_NAME, AssetsProviderAllowedActions>
>;
export type AssetsProviderMessenger =
MessengerCaller<AssetsProviderAllowedActions>;

export class AssetsProvider {
readonly #messenger: AssetsProviderMessenger;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Messenger } from '@metamask/messenger';
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
import { AsyncMessenger } from '@metamask/snaps-sdk';
import type { Json } from '@metamask/utils';

import type { MessengerCaller } from '../../types/messenger-caller';

/**
* Namespace for the {@link RemoteFeatureFlagsProvider} messenger.
*/
Expand All @@ -16,14 +16,14 @@ export type RemoteFeatureFlagsProviderAllowedActions =
RemoteFeatureFlagControllerGetStateAction;

/**
* Messenger restricted to actions consumed by {@link RemoteFeatureFlagsProvider}.
* Messenger caller for actions consumed by {@link RemoteFeatureFlagsProvider}.
*
* Namespace-agnostic so a Snap Core `getMessenger` endowment (which uses the
* Snap's own namespace and may expose additional actions) is assignable without
* casts.
*/
export type RemoteFeatureFlagsProviderMessenger = AsyncMessenger<
Messenger<
typeof REMOTE_FEATURE_FLAGS_PROVIDER_NAME,
RemoteFeatureFlagsProviderAllowedActions
>
>;
export type RemoteFeatureFlagsProviderMessenger =
MessengerCaller<RemoteFeatureFlagsProviderAllowedActions>;

export class RemoteFeatureFlagsProvider {
readonly #messenger: RemoteFeatureFlagsProviderMessenger;
Expand Down
51 changes: 51 additions & 0 deletions packages/snap-networks-utils/src/types/messenger-caller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type {
AssetsControllerGetAccountAssetByIDAction,
AssetsControllerGetAccountAssetsByIDsAction,
AssetsControllerGetAccountAssetsByScopeAction,
} from '@metamask/assets-controller';
import type { Messenger } from '@metamask/messenger';
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
import type { AsyncMessenger } from '@metamask/snaps-sdk';

import type { MessengerCaller } from './messenger-caller';

type AssetsActions =
| AssetsControllerGetAccountAssetByIDAction
| AssetsControllerGetAccountAssetsByIDsAction
| AssetsControllerGetAccountAssetsByScopeAction;

type CoreActions = AssetsActions | RemoteFeatureFlagControllerGetStateAction;

type CoreAsyncMessenger = AsyncMessenger<
Messenger<'TestSnapMessenger', CoreActions>
>;

/**
* Compile-time assertion helper. If `Value` is not assignable to `Target`,
* TypeScript fails this file.
*
* @param value - Value that must be assignable to `Target`.
* @returns The same value.
*/
function assertAssignable<Target>(value: Target): Target {
return value;
}

describe('MessengerCaller', () => {
it('accepts a Core AsyncMessenger that exposes a superset of actions', () => {
const coreMessenger = {
call: jest.fn(),
} as unknown as CoreAsyncMessenger;

// These assignments must typecheck without casts.
const assetsCaller: MessengerCaller<AssetsActions> =
assertAssignable<MessengerCaller<AssetsActions>>(coreMessenger);
const flagsCaller: MessengerCaller<RemoteFeatureFlagControllerGetStateAction> =
assertAssignable<
MessengerCaller<RemoteFeatureFlagControllerGetStateAction>
>(coreMessenger);

expect(assetsCaller).toBe(coreMessenger);
expect(flagsCaller).toBe(coreMessenger);
});
});
45 changes: 45 additions & 0 deletions packages/snap-networks-utils/src/types/messenger-caller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type {
ActionConstraint,
ExtractActionParameters,
ExtractActionResponse,
} from '@metamask/messenger';

/**
* Ability to call a single messenger action through an async Snap messenger.
*
* Prefer composing these with {@link MessengerCaller} rather than using
* `AsyncMessenger<Messenger<'SomeNamespace', Actions>>` directly: Snap
* `getMessenger` endowments use the Snap's own namespace and often expose a
* superset of actions, so namespace-bound `AsyncMessenger` types force unsafe
* casts at the call site.
*/
export type CanCall<Action extends ActionConstraint> = {
call(
actionType: Action['type'],
...params: ExtractActionParameters<Action, Action['type']>
): Promise<Awaited<ExtractActionResponse<Action, Action['type']>>>;
};

/**
* Convert a union to an intersection (distributive).
*
* @template Union - Union to convert.
*/
type UnionToIntersection<Union> = (
Union extends unknown ? (value: Union) => void : never
) extends (value: infer Intersection) => void
? Intersection
: never;

/**
* Namespace-agnostic caller for a fixed set of messenger actions.
*
* Distributes {@link CanCall} over an action union so a Snap Core messenger
* that exposes a superset of actions remains assignable without casts.
*
* @template Actions - Action constraint union the caller must support.
*/
export type MessengerCaller<Actions extends ActionConstraint> =
UnionToIntersection<
Actions extends ActionConstraint ? CanCall<Actions> : never
>;
1 change: 1 addition & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Scope `bip44:discover` activity checks and account creation to the networks declared in the snap manifest, preventing unnecessary calls to testnet APIs during discovery ([#135](https://github.com/MetaMask/internal-snaps/pull/135))
- Type Core messenger as `AsyncMessenger` and pass it to shared providers without casts ([#138](https://github.com/MetaMask/internal-snaps/pull/138))

## [3.0.0]

Expand Down
15 changes: 7 additions & 8 deletions packages/tron-wallet-snap/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ import {
AssetsProvider,
RemoteFeatureFlagsProvider,
} from '@metamask/snap-networks-utils';
import type {
AssetsProviderMessenger,
RemoteFeatureFlagsProviderMessenger,
} from '@metamask/snap-networks-utils';
import { getMessenger } from '@metamask/snaps-sdk';

import { InMemoryCache } from './caching/InMemoryCache';
Expand Down Expand Up @@ -39,7 +35,10 @@ import { TransactionScanService } from './services/transaction-scan/TransactionS
import { TransactionsRepository } from './services/transactions/TransactionsRepository';
import { TransactionsService } from './services/transactions/TransactionsService';
import { WalletService } from './services/wallet/WalletService';
import type { CoreMessenger } from './types/core-messenger';
import type {
CoreMessenger,
CoreMessengerConstraint,
} from './types/core-messenger';
import logger, { noOpLogger } from './utils/logger';

/**
Expand Down Expand Up @@ -96,12 +95,12 @@ const tokenApiClient = new TokenApiClient(configProvider);
/**
* Core controllers plumbing
*/
const coreMessenger = getMessenger<CoreMessenger>();
const coreMessenger = getMessenger<CoreMessengerConstraint>();
const remoteFeatureFlagsProvider = new RemoteFeatureFlagsProvider({
messenger: coreMessenger as RemoteFeatureFlagsProviderMessenger,
messenger: coreMessenger,
});
const assetsProvider = new AssetsProvider({
messenger: coreMessenger as AssetsProviderMessenger,
messenger: coreMessenger,
});

// Security Alerts API client
Expand Down
11 changes: 9 additions & 2 deletions packages/tron-wallet-snap/src/types/core-messenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
} from '@metamask/assets-controller';
import type { Messenger } from '@metamask/messenger';
import type { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
import type { AsyncMessenger } from '@metamask/snaps-sdk';

/**
* Namespace for this Snap's Core messenger endowment.
Expand All @@ -18,9 +19,15 @@ export type CoreMessengerActions =
| AssetsControllerGetAccountAssetsByScopeAction;

/**
* Messenger type passed to `getMessenger` for Core controller actions.
* Sync messenger constraint passed to `getMessenger` for Core controller
* actions. Runtime value is {@link CoreMessenger}.
*/
export type CoreMessenger = Messenger<
export type CoreMessengerConstraint = Messenger<
typeof TRON_WALLET_SNAP_MESSENGER_NAMESPACE,
CoreMessengerActions
>;

/**
* Async messenger returned by `getMessenger` for Core controller actions.
*/
export type CoreMessenger = AsyncMessenger<CoreMessengerConstraint>;