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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifierEnding": "js",
"typescript.experimental.useTsgo": true,
// https://github.com/microsoft/typescript-go/issues/2780
"typescript.experimental.useTsgo": false,
"js/ts.implicitProjectConfig.module": "NodeNext",
"js/ts.implicitProjectConfig.target": "ESNext",
"editor.formatOnSave": true,
Expand Down
54 changes: 26 additions & 28 deletions packages/mask/background/services/identity/persona/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@
import { queryPersonasWithPrivateKey } from '../../../database/persona/web.js'
import { openPopupWindow } from '../../helper/popup-opener.js'

async function getIdentifier(message: unknown, identifier?: ECKeyIdentifier, source?: string, silent = false) {
if (!identifier || !silent) {
const requestID = crypto.randomUUID()
await openPopupWindow(PopupRoutes.PersonaSignRequest, {
message: JSON.stringify(message),
requestID,
identifier: identifier?.toText(),
source,
})

return timeout(
new Promise<PersonaIdentifier>((resolve, reject) => {
MaskMessages.events.personaSignRequest.on((approval) => {
if (approval.requestID !== requestID) return
if (!approval.selectedPersona) reject(new Error('The user refused to sign message with persona.'))
resolve(approval.selectedPersona!)
})
}),
60 * 1000,
'Timeout of signing with persona.',
)
}
return identifier
}
/**
* Generate a signature w or w/o confirmation from user
*/
Expand All @@ -21,37 +45,11 @@
source?: string,
silent = false,
): Promise<string> {
const getIdentifier = async () => {
if (!identifier || !silent) {
const requestID = crypto.randomUUID()
await openPopupWindow(PopupRoutes.PersonaSignRequest, {
message: JSON.stringify(message),
requestID,
identifier: identifier?.toText(),
source,
})

return timeout(
new Promise<PersonaIdentifier>((resolve, reject) => {
MaskMessages.events.personaSignRequest.on((approval) => {
if (approval.requestID !== requestID) return
if (!approval.selectedPersona)
reject(new Error('The user refused to sign message with persona.'))
resolve(approval.selectedPersona!)
})
}),
60 * 1000,
'Timeout of signing with persona.',
)
}
return identifier
}

const identifier_ = await getIdentifier()
identifier = await getIdentifier(message, identifier, source, silent)

// find the persona with the signer's identifier
const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier_)
const persona = (await queryPersonasWithPrivateKey()).find((x) => x.identifier === identifier)
if (!persona?.privateKey.d) throw new Error('Persona not found')

return Signer.sign(type, Buffer.from(fromBase64URL(persona.privateKey.d)), message)

Check failure on line 54 in packages/mask/background/services/identity/persona/sign.ts

View workflow job for this annotation

GitHub Actions / type-check

No overload matches this call.
}
38 changes: 23 additions & 15 deletions packages/mask/background/services/wallet/services/send.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JsonRpcRequest } from 'web3-types'
import { ECKeyIdentifier, type SignType } from '@masknet/shared-base'
import { ECKeyIdentifier, SignType } from '@masknet/shared-base'
import { EVMRequestReadonly, EVMWeb3Readonly } from '@masknet/web3-providers'
import {
ChainId,
Expand All @@ -8,10 +8,10 @@ import {
EthereumMethodType,
PayloadEditor,
type TransactionOptions,
Signer,
} from '@masknet/web3-shared-evm'
import { signWithWallet } from './wallet/index.js'
import { signWithPersona } from '../../identity/persona/sign.js'
import type { TransactionSerializable } from 'viem'

/**
* The entrance of all RPC requests to MaskWallet.
Expand All @@ -23,25 +23,33 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions
from,
chainId = options?.chainId ?? ChainId.Mainnet,
signableMessage,
signableConfig,
signableTransaction,
} = PayloadEditor.fromPayload(payload, options)
const identifier = ECKeyIdentifier.from(options?.identifier).unwrapOr(undefined)
const signer =
identifier ?
new Signer(identifier, <T>(type: SignType, message: T, identifier?: ECKeyIdentifier) =>
signWithPersona(type, message, identifier, undefined, true),
)
: new Signer(owner || from!, signWithWallet)
const signTransaction = async (transaction: TransactionSerializable) => {
if (identifier) {
return signWithPersona(SignType.Transaction, transaction, identifier)
} else {
return signWithWallet(SignType.Transaction, transaction, owner || from!)
}
}
const signMessageOrTypedData = async (type: SignType.Message | SignType.TypedData, message: string) => {
if (identifier) {
return signWithPersona(type, message, identifier)
} else {
return signWithWallet(type, message, owner || from!)
}
}

switch (payload.method) {
case EthereumMethodType.eth_sendTransaction:
case EthereumMethodType.MASK_REPLACE_TRANSACTION:
if (!signableConfig) throw new Error('No transaction to be sent.')
if (!signableTransaction) throw new Error('No transaction to be sent.')

try {
return createJsonRpcResponse(
pid,
await EVMWeb3Readonly.sendSignedTransaction(await signer.signTransaction(signableConfig), {
await EVMWeb3Readonly.sendSignedTransaction(await signTransaction(signableTransaction), {
chainId,
providerURL,
}),
Expand All @@ -53,21 +61,21 @@ export async function send(payload: JsonRpcRequest, options?: TransactionOptions
case EthereumMethodType.personal_sign:
try {
if (!signableMessage) throw new Error('No message to be signed.')
return createJsonRpcResponse(pid, await signer.signMessage(signableMessage))
return createJsonRpcResponse(pid, await signMessageOrTypedData(SignType.Message, signableMessage))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign message.').error
}
case EthereumMethodType.eth_signTypedData_v4:
try {
if (!signableMessage) throw new Error('No typed data to be signed.')
return createJsonRpcResponse(pid, await signer.signTypedData(signableMessage))
return createJsonRpcResponse(pid, await signMessageOrTypedData(SignType.TypedData, signableMessage))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign typed data.').error
}
case EthereumMethodType.eth_signTransaction:
try {
if (!signableConfig) throw new Error('No transaction to be signed.')
return createJsonRpcResponse(pid, await signer.signTransaction(signableConfig))
if (!signableTransaction) throw new Error('No transaction to be signed.')
return createJsonRpcResponse(pid, await signTransaction(signableTransaction))
} catch (error) {
throw ErrorEditor.from(error, null, 'Failed to sign transaction.').error
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { EVMWeb3 } from '@masknet/web3-providers'
import { useNftRedPacketContract } from './useNftRedPacketContract.js'
import type { RedPacketNftJSONPayload } from '@masknet/web3-providers/types'
import { useSignedMessage } from './useSignedMessage.js'
import { useMemo } from 'react'
import type { Hex } from 'viem'

const EXTRA_GAS_PER_NFT = 335

Expand All @@ -16,12 +16,10 @@ export function useClaimNftRedpacketCallback(payload: RedPacketNftJSONPayload, t
const nftRedPacketContract = useNftRedPacketContract(chainId)
const { refetch } = useSignedMessage(account, payload)
const id = payload.id
const signedMsg = useMemo(() => {
return signMessage(account, payload.privateKey).signature ?? ''
}, [account, payload.privateKey])
return useAsyncFn(async () => {
if (!nftRedPacketContract || !id || !account || !totalAmount || !signedMsg) return
if (!nftRedPacketContract || !id || !account || !totalAmount || !payload.privateKey) return

const signedMsg = await signMessage(account, payload.privateKey as Hex)
const transaction = nftRedPacketContract.methods.claim(id, signedMsg, account)
const estimatedGas = await transaction.estimateGas({ from: account })
const tx = await new ContractTransaction(nftRedPacketContract).fillAll(transaction, {
Expand All @@ -30,5 +28,5 @@ export function useClaimNftRedpacketCallback(payload: RedPacketNftJSONPayload, t
chainId,
})
return EVMWeb3.sendTransaction(tx, { chainId })
}, [id, refetch, account, chainId, totalAmount, signedMsg])
}, [id, refetch, account, chainId, totalAmount, account, payload.privateKey])
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { type RedPacketJSONPayload, type RedPacketNftJSONPayload } from '@maskne
import { signMessage } from '@masknet/web3-shared-evm'
import { useQuery } from '@tanstack/react-query'
import { usePlatformType } from './usePlatformType.js'
import type { Hex } from 'viem'

// TODO NFT redpacket is not supported by the API yet.
export function useSignedMessage(
Expand Down Expand Up @@ -34,7 +35,7 @@ export function useSignedMessage(
queryKey: ['red-packet', 'signed-message', rpid, version, password, account, profile, isTokenRedPacket],
queryFn: async () => {
if (isTokenRedPacket && version <= 3) return password ?? null
if (password) return signMessage(account, password).signature
if (password) return signMessage(account, password as Hex)
if (!profile) return ''
return (
(await FireflyRedPacket.createClaimSignature({
Expand Down
23 changes: 19 additions & 4 deletions packages/web3-providers/src/Web3/EVM/apis/SignerAPI.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import defer * as _metamask_eth_sig_util from '@metamask/eth-sig-util'
import { signTransaction, type Transaction } from '@masknet/web3-shared-evm'
import { signTransaction } from '@masknet/web3-shared-evm'
import { SignType, toHex } from '@masknet/shared-base'
import { unreachable } from '@masknet/kit'
import type { Hex, TransactionSerializable } from 'viem'

export class Signer {
static async sign(type: SignType, key: Buffer<ArrayBuffer>, message: unknown): Promise<string> {
static async sign(
type: SignType.Message | SignType.TypedData,
key: Buffer<ArrayBuffer>,
message: string,
): Promise<string>
static async sign(
type: SignType.Transaction,
key: Buffer<ArrayBuffer>,
message: TransactionSerializable,
): Promise<string>
static async sign(
type: SignType,
key: Buffer<ArrayBuffer>,
message: string | TransactionSerializable,
): Promise<string> {
switch (type) {
case SignType.Message:
return _metamask_eth_sig_util.personalSign({
Expand All @@ -18,12 +33,12 @@ export class Signer {
version: _metamask_eth_sig_util.SignTypedDataVersion.V4,
})
case SignType.Transaction:
const transaction = message as Transaction
const transaction = message as TransactionSerializable

const chainId = transaction.chainId
if (!chainId) throw new Error('Invalid chain id.')

const { rawTransaction } = await signTransaction(transaction, toHex(key))
const rawTransaction = await signTransaction(transaction, toHex(key) as Hex)
if (!rawTransaction) throw new Error('Failed to sign transaction.')
return rawTransaction

Expand Down
12 changes: 5 additions & 7 deletions packages/web3-shared/evm/src/helpers/createAccount.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Account } from 'web3-core'
import defer * as Web3Accounts from 'web3-eth-accounts'
import type { Accounts } from 'web3-eth-accounts'
import { generatePrivateKey, privateKeyToAddress } from 'viem/accounts'

export function createAccount(): Account {
const Accounts_ = Web3Accounts.default as unknown as typeof Accounts
const accounts = new Accounts_()
return accounts.create()
export function createAccount() {
const privateKey = generatePrivateKey()
const address = privateKeyToAddress(privateKey)
return { privateKey, address }
}
13 changes: 7 additions & 6 deletions packages/web3-shared/evm/src/helpers/signMessage.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import defer * as Web3Accounts from 'web3-eth-accounts'
import type { Accounts } from 'web3-eth-accounts'
import type { Hex } from 'viem'
import { signMessage as viem_sign } from 'viem/accounts'

export function signMessage(message: string, privateKey: string): Web3Accounts.Sign {
const Accounts_ = Web3Accounts.default as unknown as typeof Accounts
const accounts = new Accounts_()
return accounts.sign(message, privateKey)
export function signMessage(message: string, privateKey: Hex): Promise<string> {
return viem_sign({
message: message.startsWith('0x') ? { raw: message as Hex } : message,
privateKey,
})
}
12 changes: 4 additions & 8 deletions packages/web3-shared/evm/src/helpers/signTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import defer * as Web3Accounts from 'web3-eth-accounts'
import type { Accounts } from 'web3-eth-accounts'
import type { Transaction } from '../types/index.js'
import { signTransaction as viem_signTransaction } from 'viem/accounts'
import type { Hex, TransactionSerializable } from 'viem'

export function signTransaction(transaction: Transaction, privateKey: string) {
if (typeof transaction.nonce === 'undefined') throw new Error('Nonce is required.')
const Accounts_ = Web3Accounts.default as unknown as typeof Accounts
const accounts = new Accounts_()
return accounts.signTransaction(transaction, privateKey)
export function signTransaction(transaction: TransactionSerializable, privateKey: Hex) {
return viem_signTransaction({ privateKey, transaction })
}
2 changes: 0 additions & 2 deletions packages/web3-shared/evm/src/libs/AccountTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export class AccountTransaction {
maxFeePerGas,
data,
nonce,
_disableExceptionSnackbar,
_disableSuccessSnackbar,
_disableSnackbar,
} = {
Expand All @@ -76,7 +75,6 @@ export class AccountTransaction {
maxPriorityFeePerGas: maxPriorityFeePerGas ? normalizeHex(maxPriorityFeePerGas) : undefined,
maxFeePerGas: maxFeePerGas ? normalizeHex(maxFeePerGas) : undefined,
nonce,
_disableExceptionSnackbar,
_disableSuccessSnackbar,
_disableSnackbar,
},
Expand Down
Loading
Loading