Skip to content
Draft
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
5 changes: 5 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ const config = createConfig([
typescript,
]),
...NO_CONTROLLER_STATE_CHANGE_SELECTOR_OBJECTS,
{
selector: 'TSEnumDeclaration',
message:
"Don't use enums. There are a number of reasons why they are problematic, but the most important is that TypeScript treats them nominally, not structurally, and this can cause unexpected breaking changes. Instead, use an object + type, an array + type, or just a type. Learn more here: https://github.com/MetaMask/eslint-config/issues/417",
},
],
},
},
Expand Down
8 changes: 8 additions & 0 deletions packages/bitcoin-wallet-snap/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]

### Changed

**BREAKING:** Convert `CronjobMethod`, `Slip44`, `Purpose`, `Sep43ErrorCode`, `Fiat`, `CurrencyUnit`, `AccountFeature`, `AddressType`, `BalanceChangeType`, `ConfirmationStatus`, and `TransactionType` from TypeScript enums to `as const` objects with derived union types ([#XXX](https://github.com/MetaMask/internal-snaps/pull/XXX))
- Member access (`Enum.Member`) and runtime values are unchanged
- Type signatures change: `Enum` is now a union of string/number literals instead of a nominal enum type
- `typeof Enum.Member` expressions in type positions now refer to literal values instead of enum member types


### Fixed

- Ensure certain errors are stringified correctly ([#179](https://github.com/MetaMask/internal-snaps/pull/179))
Expand Down
51 changes: 29 additions & 22 deletions packages/bitcoin-wallet-snap/src/entities/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,17 +222,20 @@ export type BitcoinAccount = {
applyUnconfirmedTx(tx: Transaction, lastSeen: number): void;
};

export enum AccountCapability {
SignPsbt = 'signPsbt',
ComputeFee = 'computeFee',
FillPsbt = 'fillPsbt',
BroadcastPsbt = 'broadcastPsbt',
SendTransfer = 'sendTransfer',
GetUtxo = 'getUtxo',
ListUtxos = 'listUtxos',
PublicDescriptor = 'publicDescriptor',
SignMessage = 'signMessage',
}
export const AccountCapability = {
SignPsbt: 'signPsbt',
ComputeFee: 'computeFee',
FillPsbt: 'fillPsbt',
BroadcastPsbt: 'broadcastPsbt',
SendTransfer: 'sendTransfer',
GetUtxo: 'getUtxo',
ListUtxos: 'listUtxos',
PublicDescriptor: 'publicDescriptor',
SignMessage: 'signMessage',
} as const;

export type AccountCapability =
(typeof AccountCapability)[keyof typeof AccountCapability];

/**
* BitcoinAccountRepository is a repository that manages Bitcoin accounts.
Expand Down Expand Up @@ -332,18 +335,22 @@ export type BitcoinAccountRepository = {
getFrozenUTXOs(id: string): Promise<string[]>;
};

export enum Purpose {
Legacy = 44,
Segwit = 49,
NativeSegwit = 84,
Taproot = 86,
Multisig = 45,
}
export const Purpose = {
Legacy: 44,
Segwit: 49,
NativeSegwit: 84,
Taproot: 86,
Multisig: 45,
} as const;

export enum Slip44 {
Bitcoin = 0,
Testnet = 1,
}
export type Purpose = (typeof Purpose)[keyof typeof Purpose];

export const Slip44 = {
Bitcoin: 0,
Testnet: 1,
} as const;

export type Slip44 = (typeof Slip44)[keyof typeof Slip44];

export const addressTypeToPurpose: Record<AddressType, Purpose> = {
p2pkh: Purpose.Legacy,
Expand Down
11 changes: 7 additions & 4 deletions packages/bitcoin-wallet-snap/src/entities/confirmation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ export type SignPsbtConfirmationContext = {
inputCount: number;
};

export enum ConfirmationEvent {
Confirm = 'confirmation-confirm',
Cancel = 'confirmation-cancel',
}
export const ConfirmationEvent = {
Confirm: 'confirmation-confirm',
Cancel: 'confirmation-cancel',
} as const;

export type ConfirmationEvent =
(typeof ConfirmationEvent)[keyof typeof ConfirmationEvent];

/**
* ConfirmationRepository is a repository that manages request confirmations for dApps.
Expand Down
16 changes: 9 additions & 7 deletions packages/bitcoin-wallet-snap/src/entities/currency.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { Network } from '@metamask/bitcoindevkit';

export enum CurrencyUnit {
Bitcoin = 'BTC',
Testnet = 'tBTC',
Signet = 'sBTC',
Regtest = 'rBTC',
Fiat = 'fiat', // Can also be cryptos like ETH, but will be fiat for 99% of users
}
export const CurrencyUnit = {
Bitcoin: 'BTC',
Testnet: 'tBTC',
Signet: 'sBTC',
Regtest: 'rBTC',
Fiat: 'fiat', // Can also be cryptos like ETH, but will be fiat for 99% of users
} as const;

export type CurrencyUnit = (typeof CurrencyUnit)[keyof typeof CurrencyUnit];

export type CurrencyRate = {
conversionRate: number;
Expand Down
37 changes: 21 additions & 16 deletions packages/bitcoin-wallet-snap/src/entities/send-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,20 @@ export type SendFormContext = {
locale: string;
};

export enum SendFormEvent {
Amount = 'amount',
Recipient = 'recipient',
ClearRecipient = 'clearRecipient',
ClearAmount = 'clearAmount',
Confirm = 'confirm',
Cancel = 'cancel',
Max = 'max',
Account = 'account',
Asset = 'asset',
SwitchCurrency = 'switchCurrency',
}
export const SendFormEvent = {
Amount: 'amount',
Recipient: 'recipient',
ClearRecipient: 'clearRecipient',
ClearAmount: 'clearAmount',
Confirm: 'confirm',
Cancel: 'cancel',
Max: 'max',
Account: 'account',
Asset: 'asset',
SwitchCurrency: 'switchCurrency',
} as const;

export type SendFormEvent = (typeof SendFormEvent)[keyof typeof SendFormEvent];

export type ReviewTransactionContext = {
from: string;
Expand All @@ -75,10 +77,13 @@ export type ReviewTransactionContext = {
sendForm?: SendFormContext;
};

export enum ReviewTransactionEvent {
Send = 'send',
HeaderBack = 'headerBack',
}
export const ReviewTransactionEvent = {
Send: 'send',
HeaderBack: 'headerBack',
} as const;

export type ReviewTransactionEvent =
(typeof ReviewTransactionEvent)[keyof typeof ReviewTransactionEvent];

/**
* SendFlowRepository is a repository that manages Bitcoin Send flow interfaces.
Expand Down
15 changes: 9 additions & 6 deletions packages/bitcoin-wallet-snap/src/entities/snap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,15 @@ export type SyncResult = {
transactionsToNotify: WalletTx[];
};

export enum TrackingSnapEvent {
TransactionFinalized = 'Transaction Finalized',
TransactionReceived = 'Transaction Received',
TransactionReorged = 'Transaction Reorged',
TransactionSubmitted = 'Transaction Submitted',
}
export const TrackingSnapEvent = {
TransactionFinalized: 'Transaction Finalized',
TransactionReceived: 'Transaction Received',
TransactionReorged: 'Transaction Reorged',
TransactionSubmitted: 'Transaction Submitted',
} as const;

export type TrackingSnapEvent =
(typeof TrackingSnapEvent)[keyof typeof TrackingSnapEvent];

/**
* The SnapClient represents the MetaMask Snap state and manages the BIP-32 entropy from the Wallet SRP.
Expand Down
14 changes: 8 additions & 6 deletions packages/bitcoin-wallet-snap/src/handlers/CronHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { InexistentMethodError, SynchronizationError } from '../entities';
import type { SnapClient, SyncResult } from '../entities';
import type { SendFlowUseCases, AccountUseCases } from '../use-cases';

export enum CronMethod {
SynchronizeAccounts = 'synchronizeAccounts',
RefreshRates = 'refreshRates',
SyncSelectedAccounts = 'syncSelectedAccounts',
FullScanAccount = 'fullScanAccount',
}
export const CronMethod = {
SynchronizeAccounts: 'synchronizeAccounts',
RefreshRates: 'refreshRates',
SyncSelectedAccounts: 'syncSelectedAccounts',
FullScanAccount: 'fullScanAccount',
} as const;

export type CronMethod = (typeof CronMethod)[keyof typeof CronMethod];

export const SendFormRefreshRatesRequest = object({
interfaceId: string(),
Expand Down
16 changes: 9 additions & 7 deletions packages/bitcoin-wallet-snap/src/handlers/caip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ export const networkToScope = reverseMapping(scopeToNetwork);

export const addressTypeToCaip = reverseMapping(caipToAddressType);

export enum Caip19Asset {
Bitcoin = 'bip122:000000000019d6689c085ae165831e93/slip44:0',
Testnet = 'bip122:000000000933ea01ad0ee984209779ba/slip44:0',
Testnet4 = 'bip122:00000000da84f2bafbbc53dee25a72ae/slip44:0',
Signet = 'bip122:00000008819873e925422c1ff0f99f7c/slip44:0',
Regtest = 'bip122:regtest/slip44:0',
}
export const Caip19Asset = {
Bitcoin: 'bip122:000000000019d6689c085ae165831e93/slip44:0',
Testnet: 'bip122:000000000933ea01ad0ee984209779ba/slip44:0',
Testnet4: 'bip122:00000000da84f2bafbbc53dee25a72ae/slip44:0',
Signet: 'bip122:00000008819873e925422c1ff0f99f7c/slip44:0',
Regtest: 'bip122:regtest/slip44:0',
} as const;

export type Caip19Asset = (typeof Caip19Asset)[keyof typeof Caip19Asset];

export const NetworkStruct = enums(Object.values(BtcScope));

Expand Down
42 changes: 23 additions & 19 deletions packages/bitcoin-wallet-snap/src/handlers/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,29 @@ import {
import type { BitcoinAccount, CodifiedError, Logger } from '../entities';
import { ValidationError } from '../entities';

export enum RpcMethod {
StartSendTransactionFlow = 'startSendTransactionFlow',
SignAndSendTransaction = 'signAndSendTransaction',
ComputeFee = 'computeFee',
VerifyMessage = 'verifyMessage',
OnAddressInput = 'onAddressInput',
OnAmountInput = 'onAmountInput',
ConfirmSend = 'confirmSend',
SignRewardsMessage = 'signRewardsMessage',
SignProofOfOwnership = 'signProofOfOwnership',
}

export enum SendErrorCodes {
// eslint-disable-next-line @typescript-eslint/no-shadow
Required = 'Required',
Invalid = 'Invalid',
InsufficientBalance = 'InsufficientBalance',
InsufficientBalanceToCoverFee = 'InsufficientBalanceToCoverFee',
}
export const RpcMethod = {
StartSendTransactionFlow: 'startSendTransactionFlow',
SignAndSendTransaction: 'signAndSendTransaction',
ComputeFee: 'computeFee',
VerifyMessage: 'verifyMessage',
OnAddressInput: 'onAddressInput',
OnAmountInput: 'onAmountInput',
ConfirmSend: 'confirmSend',
SignRewardsMessage: 'signRewardsMessage',
SignProofOfOwnership: 'signProofOfOwnership',
} as const;

export type RpcMethod = (typeof RpcMethod)[keyof typeof RpcMethod];

export const SendErrorCodes = {
Required: 'Required',
Invalid: 'Invalid',
InsufficientBalance: 'InsufficientBalance',
InsufficientBalanceToCoverFee: 'InsufficientBalanceToCoverFee',
} as const;

export type SendErrorCodes =
(typeof SendErrorCodes)[keyof typeof SendErrorCodes];

export const NonEmptyStringStruct = refine(
nonempty(string()),
Expand Down
7 changes: 7 additions & 0 deletions packages/solana-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

**BREAKING:** Convert `SolMethod`, `AccountCreationType`, `TokenFeature`, `TokenStandard`, `TransactionScanType`, `TransactionStatus`, `TransactionType`, `AccountType`, `AccountScope`, `AccountFeature`, `Scope`, `CronjobMethod`, `ScheduleBackgroundEventMethod`, `Network`, `Sep43ErrorCode`, and `Secp256Instruction` from TypeScript enums to `as const` objects with derived union types ([#XXX](https://github.com/MetaMask/internal-snaps/pull/XXX))
- Member access (`Enum.Member`) and runtime values are unchanged
- Type signatures change: `Enum` is now a union of string/number literals instead of a nominal enum type


### Changed

- Extract Snap-owned assets domain logic into `SnapAssetsAdapter`; `AssetsService` is a thin facade that delegates metadata, market data, fetch, persist, and account asset reads through the adapter (no Core routing yet). ([#121](https://github.com/MetaMask/internal-snaps/pull/121))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ export class TokenApiClient {
// The Token API only supports the networks in TokenApiClient.supportedNetworks
const supportedAssetTypes = assetTypes.filter((assetType) => {
const { chainId } = parseCaipAssetType(assetType);
return TokenApiClient.supportedNetworks.includes(chainId as Network);
return TokenApiClient.supportedNetworks.includes(
chainId as (typeof TokenApiClient.supportedNetworks)[number],
);
});

if (supportedAssetTypes.length !== assetTypes.length) {
Expand Down
53 changes: 30 additions & 23 deletions packages/solana-wallet-snap/src/core/constants/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,30 @@ export const METAMASK_ORIGIN_URL = 'https://metamask.io';
*
* @see https://namespaces.chainagnostic.org/solana/caip2
*/
export enum Network {
Mainnet = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
Devnet = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
Testnet = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
Localnet = 'solana:123456789abcdef',
}

export enum KnownCaip19Id {
SolMainnet = `${Network.Mainnet}/slip44:501`,
SolDevnet = `${Network.Devnet}/slip44:501`,
SolTestnet = `${Network.Testnet}/slip44:501`,
SolLocalnet = `${Network.Localnet}/slip44:501`,
UsdcMainnet = `${Network.Mainnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
UsdcDevnet = `${Network.Devnet}/token:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`,
UsdcLocalnet = `${Network.Localnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
EurcMainnet = `${Network.Mainnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
EurcDevnet = `${Network.Devnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
EurcLocalnet = `${Network.Localnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
Ai16zMainnet = `${Network.Mainnet}/token:HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC`,
}
export const Network = {
Mainnet: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
Devnet: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1',
Testnet: 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z',
Localnet: 'solana:123456789abcdef',
} as const;

export type Network = (typeof Network)[keyof typeof Network];

export const KnownCaip19Id = {
SolMainnet: `${Network.Mainnet}/slip44:501`,
SolDevnet: `${Network.Devnet}/slip44:501`,
SolTestnet: `${Network.Testnet}/slip44:501`,
SolLocalnet: `${Network.Localnet}/slip44:501`,
UsdcMainnet: `${Network.Mainnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
UsdcDevnet: `${Network.Devnet}/token:4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU`,
UsdcLocalnet: `${Network.Localnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`,
EurcMainnet: `${Network.Mainnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
EurcDevnet: `${Network.Devnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
EurcLocalnet: `${Network.Localnet}/token:HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr`,
Ai16zMainnet: `${Network.Mainnet}/token:HeLp6NuQkmYB4pYWo2zYs22mESHXPQYzXbB8n4V98jwC`,
} as const;

export type KnownCaip19Id = (typeof KnownCaip19Id)[keyof typeof KnownCaip19Id];

export type NativeCaipAssetType = `${Network}/slip44:501`;
export type TokenCaipAssetType = `${Network}/token:${string}`;
Expand Down Expand Up @@ -92,9 +96,12 @@ export const NETWORK_TO_EXPLORER_CLUSTER = {
[Network.Localnet]: 'local',
};

export enum SolanaCaip19Tokens {
SOL = 'slip44:501',
}
export const SolanaCaip19Tokens = {
SOL: 'slip44:501',
} as const;

export type SolanaCaip19Tokens =
(typeof SolanaCaip19Tokens)[keyof typeof SolanaCaip19Tokens];

export type TokenInfo = {
symbol: string;
Expand Down
Loading