diff --git a/packages/mask/background/services/identity/persona/sign.ts b/packages/mask/background/services/identity/persona/sign.ts index 7392136c8c3..65f67c97cb7 100644 --- a/packages/mask/background/services/identity/persona/sign.ts +++ b/packages/mask/background/services/identity/persona/sign.ts @@ -1,5 +1,6 @@ import { timeout } from '@masknet/kit' import { Signer } from '@masknet/web3-providers' +import type { ChainId } from '@masknet/web3-shared-evm' import { type PersonaIdentifier, fromBase64URL, @@ -43,6 +44,7 @@ export async function signWithPersona( identifier?: ECKeyIdentifier, origin?: string, silent = false, + chainId?: ChainId, ): Promise { identifier = await getIdentifier(message.data, identifier, origin, silent) @@ -50,5 +52,5 @@ export async function signWithPersona( const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier) if (!persona?.privateKey.d) throw new Error('Persona not found') - return Signer.sign(message, Buffer.from(fromBase64URL(persona.privateKey.d))) + return Signer.sign(message, Buffer.from(fromBase64URL(persona.privateKey.d)), chainId) } diff --git a/packages/mask/background/services/wallet/services/send.ts b/packages/mask/background/services/wallet/services/send.ts index aa3475e3c54..84b669f0938 100644 --- a/packages/mask/background/services/wallet/services/send.ts +++ b/packages/mask/background/services/wallet/services/send.ts @@ -1,6 +1,6 @@ import type { JsonRpcRequest } from 'web3-types' import { ECKeyIdentifier, SignType } from '@masknet/shared-base' -import { EVMRequestReadonly, EVMWeb3Readonly } from '@masknet/web3-providers' +import { EVMRequestReadonly, EVMWalletProviders, EVMWeb3Readonly } from '@masknet/web3-providers' import { ChainId, createJsonRpcResponse, @@ -25,13 +25,22 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions signableMessage, signableTransaction, } = PayloadEditor.fromPayload(payload, options) + const isTransactionSigningMethod = + payload.method === EthereumMethodType.eth_sendTransaction || + payload.method === EthereumMethodType.MASK_REPLACE_TRANSACTION || + payload.method === EthereumMethodType.eth_signTransaction + const providerChainId = + options?.providerType && isTransactionSigningMethod ? + EVMWalletProviders[options.providerType].subscription.chainId.getCurrentValue() + : undefined + const requestChainId = providerChainId ?? chainId const identifier = ECKeyIdentifier.from(options?.identifier).unwrapOr(undefined) const signTransaction = async (transaction: TransactionSerializable) => { const message = { type: SignType.Transaction as const, data: transaction } if (identifier) { - return signWithPersona(message, identifier) + return signWithPersona(message, identifier, undefined, false, providerChainId) } else { - return signWithWallet(message, owner || from!) + return signWithWallet(message, owner || from!, providerChainId) } } const signMessageOrTypedData = async (type: SignType.Message | SignType.TypedData, message: string) => { @@ -47,12 +56,19 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions case EthereumMethodType.eth_sendTransaction: case EthereumMethodType.MASK_REPLACE_TRANSACTION: if (!signableTransaction) throw new Error('No transaction to be sent.') + if ( + providerChainId !== undefined && + signableTransaction.chainId !== undefined && + signableTransaction.chainId !== providerChainId + ) { + throw new Error('Chain ID mismatch.') + } try { return createJsonRpcResponse( pid, await EVMWeb3Readonly.sendSignedTransaction(await signTransaction(signableTransaction), { - chainId, + chainId: requestChainId, providerURL, }), ) diff --git a/packages/mask/background/services/wallet/services/wallet/index.ts b/packages/mask/background/services/wallet/services/wallet/index.ts index 5064b0b7a39..0b80c2a458a 100644 --- a/packages/mask/background/services/wallet/services/wallet/index.ts +++ b/packages/mask/background/services/wallet/services/wallet/index.ts @@ -5,6 +5,7 @@ import { api } from '@dimensiondev/mask-wallet-core/proto' import { Signer } from '@masknet/web3-providers' import { ImportSource, toHex, type SignMessage, type Wallet } from '@masknet/shared-base' import { HD_PATH_WITHOUT_INDEX_ETHEREUM } from '@masknet/web3-shared-base' +import type { ChainId } from '@masknet/web3-shared-evm' import * as Mask from '../maskwallet/index.js' import * as database from './database/index.js' import * as password from './password.js' @@ -247,8 +248,8 @@ export async function resetAllWallets() { await database.resetAllWallets() } -export async function signWithWallet(message: SignMessage, address: string) { - return Signer.sign(message, Buffer.from(toBytes(`0x${await exportPrivateKey(address)}`))) +export async function signWithWallet(message: SignMessage, address: string, chainId?: ChainId) { + return Signer.sign(message, Buffer.from(toBytes(`0x${await exportPrivateKey(address)}`)), chainId) } export async function exportMnemonicWords(address: string, unverifiedPassword?: string) { diff --git a/packages/web3-providers/src/Web3/EVM/apis/RequestAPI.ts b/packages/web3-providers/src/Web3/EVM/apis/RequestAPI.ts index 2ad2d0a3ec9..428bebc8f89 100644 --- a/packages/web3-providers/src/Web3/EVM/apis/RequestAPI.ts +++ b/packages/web3-providers/src/Web3/EVM/apis/RequestAPI.ts @@ -1,4 +1,5 @@ -import { EthereumMethodType, PayloadEditor, type RequestArguments } from '@masknet/web3-shared-evm' +import { EthereumMethodType, PayloadEditor, type ChainId, type RequestArguments } from '@masknet/web3-shared-evm' +import type { TransactionSerializable } from 'viem' import { Composer } from './ComposerAPI.js' import { evm } from '../../../Manager/registry.js' import { ConnectionOptionsAPI } from './ConnectionOptionsAPI.js' @@ -9,6 +10,12 @@ import type { EVMConnectionOptions } from '../types/index.js' import { createWeb3FromProvider } from '../../../helpers/createWeb3FromProvider.js' import { createWeb3ProviderFromRequest } from '../../../helpers/createWeb3ProviderFromRequest.js' +function assertTransactionChainId(transaction: TransactionSerializable | undefined, chainId: ChainId) { + if (!transaction) return + if (transaction.chainId !== undefined && transaction.chainId !== chainId) + throw new Error('Transaction chain id does not match current chain id.') +} + export class EVMRequestAPI extends EVMRequestReadonlyAPI { static override Default = new EVMRequestAPI() private Request = new EVMRequestReadonlyAPI(this.options) @@ -46,7 +53,15 @@ export class EVMRequestAPI extends EVMRequestReadonlyAPI { context.write(await this.Provider?.disconnect(options.providerType)) break default: { - if (!PayloadEditor.fromPayload(context.request).readonly) { + const payloadEditor = PayloadEditor.fromPayload(context.request) + if (!payloadEditor.readonly) { + assertTransactionChainId( + payloadEditor.signableTransaction, + EVMWalletProviders[ + options.providerType + ].subscription.chainId.getCurrentValue(), + ) + const web3Provider = EVMWalletProviders[ options.providerType ].createWeb3Provider({ diff --git a/packages/web3-providers/src/Web3/EVM/apis/SignerAPI.ts b/packages/web3-providers/src/Web3/EVM/apis/SignerAPI.ts index 7a64d78b141..d403efc9698 100644 --- a/packages/web3-providers/src/Web3/EVM/apis/SignerAPI.ts +++ b/packages/web3-providers/src/Web3/EVM/apis/SignerAPI.ts @@ -1,10 +1,10 @@ import defer * as _metamask_eth_sig_util from '@metamask/eth-sig-util' -import { signTransaction } from '@masknet/web3-shared-evm' +import { type ChainId, signTransaction } from '@masknet/web3-shared-evm' import { type SignMessage, SignType, toHex } from '@masknet/shared-base' import { unreachable } from '@masknet/kit' export class Signer { - static async sign({ type, data }: SignMessage, key: Buffer): Promise { + static async sign({ type, data }: SignMessage, key: Buffer, chainId?: ChainId): Promise { switch (type) { case SignType.Message: return _metamask_eth_sig_util.personalSign({ @@ -20,10 +20,10 @@ export class Signer { case SignType.Transaction: const transaction = data - const chainId = transaction.chainId - if (!chainId) throw new Error('Invalid chain id.') + const transactionChainId = transaction.chainId + if (!transactionChainId) throw new Error('Invalid chain id.') - const rawTransaction = await signTransaction(transaction, toHex(key)) + const rawTransaction = await signTransaction(transaction, toHex(key), chainId) if (!rawTransaction) throw new Error('Failed to sign transaction.') return rawTransaction diff --git a/packages/web3-providers/src/Web3/EVM/interceptors/Popups.ts b/packages/web3-providers/src/Web3/EVM/interceptors/Popups.ts index e03cc6f7134..474374975d3 100644 --- a/packages/web3-providers/src/Web3/EVM/interceptors/Popups.ts +++ b/packages/web3-providers/src/Web3/EVM/interceptors/Popups.ts @@ -57,6 +57,7 @@ export class Popups implements Middleware { arguments: context.requestArguments, options: { silent: context.silent, + providerType: context.providerType, providerURL: context.providerURL, gasOptionType: context.gasOptionType, }, diff --git a/packages/web3-providers/src/entry.ts b/packages/web3-providers/src/entry.ts index 9f1ef3cbcca..d0ceac34a19 100644 --- a/packages/web3-providers/src/entry.ts +++ b/packages/web3-providers/src/entry.ts @@ -19,7 +19,7 @@ export { RedPacket } from './RedPacket/index.js' export { SnapshotSearch } from './Snapshot/index.js' export { Snapshot } from './Snapshot/index.js' -export { MaskWalletProviderInstance as MaskWalletProvider } from './Web3/EVM/providers/index.js' +export { EVMWalletProviders, MaskWalletProviderInstance as MaskWalletProvider } from './Web3/EVM/providers/index.js' // Web3 export { getConnection } from './Web3/Router/apis/getConnection.js' diff --git a/packages/web3-shared/evm/src/helpers/signTransaction.ts b/packages/web3-shared/evm/src/helpers/signTransaction.ts index 973a20e14ab..9cd508dbf23 100644 --- a/packages/web3-shared/evm/src/helpers/signTransaction.ts +++ b/packages/web3-shared/evm/src/helpers/signTransaction.ts @@ -1,6 +1,8 @@ import { signTransaction as viem_signTransaction } from 'viem/accounts' import type { Hex, TransactionSerializable } from 'viem' -export function signTransaction(transaction: TransactionSerializable, privateKey: Hex) { +export function signTransaction(transaction: TransactionSerializable, privateKey: Hex, chainId?: number) { + if (chainId !== undefined && transaction.chainId !== chainId) + throw new Error('Transaction chain id does not match current chain id.') return viem_signTransaction({ privateKey, transaction }) } diff --git a/packages/web3-shared/evm/src/types/index.ts b/packages/web3-shared/evm/src/types/index.ts index df81dac059d..3a72e3ff61f 100644 --- a/packages/web3-shared/evm/src/types/index.ts +++ b/packages/web3-shared/evm/src/types/index.ts @@ -387,6 +387,7 @@ export interface RequestOptions { silent?: boolean owner?: string identifier?: string + providerType?: ProviderType providerURL?: string gasOptionType?: GasOptionType maxFeePerGas?: string @@ -428,6 +429,7 @@ export interface TransactionOptions { chainId?: ChainId owner?: string identifier?: string + providerType?: ProviderType providerURL?: string // popups control