Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for EIP-7702 #1735

Draft
wants to merge 2 commits into
base: develop
Choose a base branch
from
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
10 changes: 9 additions & 1 deletion @types/frame/rpc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type RPCRequestCallback = RPCCallback<RPCResponsePayload>

type Address = string // 20 hex bytes, 0x-prefixed
type Caip2ChainId = string // format: "<namespace>:<chainId>", ex: "eip155:1"
type TransactionType = '0x0' | '0x1' | '0x2' | '0x4'

interface RPCId {
id: number
Expand Down Expand Up @@ -151,6 +152,12 @@ declare namespace RPC {
}

namespace SendTransaction {
interface Authorization {
contractAddress: Address
chainId: string
nonce: string
}

interface TxParams {
nonce?: string
gasPrice?: string
Expand All @@ -163,7 +170,8 @@ declare namespace RPC {
data?: string
value?: string
chainId: string
type?: string
type?: TransactionType
authorizationList?: Authorization[]
}

interface Request extends Omit<RPCRequestPayload, 'method'> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ class ViewData extends React.Component {
decodeRawTx(tx) {
const decodeTx = {}
Object.keys(tx).forEach((key) => {
if (tx[key] && !tx[key].startsWith('0x')) {
console.log('key: ', key)
if (key === 'authorizationList') {
decodeTx[key] = tx[key].toString()
} else if (tx[key] && !tx[key].startsWith('0x')) {
decodeTx[key] = tx[key]
} else if (
[
Expand Down
34 changes: 19 additions & 15 deletions main/accounts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,38 @@ import { v5 as uuidv5 } from 'uuid'
import provider from '../provider'
import store from '../store'
import FrameAccount from './Account'
import ExternalDataScanner, { DataScanner } from '../externalData'
import ExternalDataScanner from '../externalData'
import Signer from '../signers/Signer'
import { signerCompatibility as transactionCompatibility, maxFee, SignerCompatibility } from '../transaction'
import { signerCompatibility as transactionCompatibility, maxFee } from '../transaction'

import { weiIntToEthInt, hexToInt } from '../../resources/utils'
import { accountPanelCrumb, signerPanelCrumb } from '../../resources/domain/nav'
import { usesBaseFee, TransactionData, GasFeesSource } from '../../resources/domain/transaction'
import { findUnavailableSigners, isSignerReady } from '../../resources/domain/signer'

import {
import { ReplacementType, RequestStatus, RequestMode } from './types'

import { openBlockExplorer } from '../windows/window'
import { ApprovalType } from '../../resources/constants'
import { accountNS } from '../../resources/domain/account'
import { chainUsesOptimismFees } from '../../resources/utils/chains'

import type { PrefixedHexString } from '@ethereumjs/util'

import type { Chain } from '../chains'
import type { SignerCompatibility } from '../transaction'
import type { ActionType } from '../transaction/actions'
import type { DataScanner } from '../externalData'

import type {
AccountRequest,
AccessRequest,
TransactionRequest,
TransactionReceipt,
ReplacementType,
RequestStatus,
RequestMode,
TypedMessage,
PermitSignatureRequest
} from './types'

import type { Chain } from '../chains'
import { ActionType } from '../transaction/actions'
import { openBlockExplorer } from '../windows/window'
import { ApprovalType } from '../../resources/constants'
import { accountNS } from '../../resources/domain/account'
import { chainUsesOptimismFees } from '../../resources/utils/chains'

function notify(title: string, body: string, action: (event: Electron.Event) => void) {
const notification = new Notification({ title, body })
notification.on('click', action)
Expand Down Expand Up @@ -158,7 +162,7 @@ export class Accounts extends EventEmitter {
return this._current ? this.accounts[this._current] : null
}

updateNonce(reqId: string, nonce: string) {
updateNonce(reqId: string, nonce: PrefixedHexString) {
log.info('Update Nonce: ', reqId, nonce)

const currentAccount = this.current()
Expand Down Expand Up @@ -1215,7 +1219,7 @@ export class Accounts extends EventEmitter {
const txRequest = this.getTransactionRequest(currentAccount, handlerId)
const initialNonce = txRequest.payload.params[0].nonce
if (initialNonce) {
txRequest.data.nonce = initialNonce
txRequest.data.nonce = initialNonce as PrefixedHexString
} else {
delete txRequest.data.nonce
}
Expand Down
6 changes: 4 additions & 2 deletions main/contracts/erc20.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import provider from '../provider'
import { TransactionDescription } from '@ethersproject/abi'
import { Contract } from '@ethersproject/contracts'
import { Web3Provider } from '@ethersproject/providers'
import { addHexPrefix } from '@ethereumjs/util'
import provider from '../provider'
import { BigNumber } from 'ethers'
import { erc20Interface } from '../../resources/contracts'

import type { PrefixedHexString } from '@ethereumjs/util'

export interface TokenData {
decimals?: number
name: string
Expand Down Expand Up @@ -75,7 +77,7 @@ export default class Erc20Contract {
}

static encodeCallData(fn: string, params: any[]) {
return erc20Interface.encodeFunctionData(fn, params)
return erc20Interface.encodeFunctionData(fn, params) as PrefixedHexString
}

async getTokenData(): Promise<TokenData> {
Expand Down
46 changes: 30 additions & 16 deletions main/provider/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
padToEven,
unpadHexString,
addHexPrefix,
stripHexPrefix,
intToHex,
toBuffer,
pubToAddress,
ecrecover,
hashPersonalMessage
hashPersonalMessage,
toBytes,
unpadHex
} from '@ethereumjs/util'
import log from 'electron-log'
import BN from 'bignumber.js'
Expand All @@ -16,10 +16,12 @@ import { isHexString } from 'ethers/lib/utils'

import store from '../store'
import protectedMethods from '../api/protectedMethods'
import { usesBaseFee, TransactionData, GasFeesSource } from '../../resources/domain/transaction'
import { usesBaseFee, GasFeesSource } from '../../resources/domain/transaction'
import { getAddress } from '../../resources/utils'

import type { Chain, Permission } from '../store/state'
import type { TransactionData } from '../../resources/domain/transaction'
import type { Chain } from '../store/state'
import type { PrefixedHexString } from '@ethereumjs/util'

const permission = (date: number, method: string) => ({ parentCapability: method, date })

Expand Down Expand Up @@ -53,8 +55,8 @@ export function checkExistingNonceGas(tx: TransactionData) {
// Bump fees by 10%
const bumpedFee = Math.max(Math.ceil(existingFee * 1.1), feeInt)
const bumpedBase = Math.max(Math.ceil((existingMax - existingFee) * 1.1), Math.ceil(maxInt - feeInt))
tx.maxFeePerGas = '0x' + (bumpedBase + bumpedFee).toString(16)
tx.maxPriorityFeePerGas = '0x' + bumpedFee.toString(16)
tx.maxFeePerGas = intToHex(bumpedBase + bumpedFee)
tx.maxPriorityFeePerGas = intToHex(bumpedFee)
tx.gasFeesSource = GasFeesSource.Frame
tx.feesUpdated = true
}
Expand All @@ -64,7 +66,7 @@ export function checkExistingNonceGas(tx: TransactionData) {
if (existingPrice >= priceInt) {
// Bump price by 10%
const bumpedPrice = Math.ceil(existingPrice * 1.1)
tx.gasPrice = '0x' + bumpedPrice.toString(16)
tx.gasPrice = intToHex(bumpedPrice)
tx.gasFeesSource = GasFeesSource.Frame
tx.feesUpdated = true
}
Expand All @@ -85,15 +87,15 @@ export function feeTotalOverMax(rawTx: TransactionData, maxTotalFee: number) {

function parseValue(value = '') {
const parsedHex = parseInt(value, 16)
return (!!parsedHex && addHexPrefix(unpadHexString(value))) || '0x0'
return (!!parsedHex && addHexPrefix(unpadHex(value))) || '0x0'
}

export function getRawTx(newTx: RPC.SendTransaction.TxParams): TransactionData {
const { gas, gasLimit, data, value, type, from, to, ...rawTx } = newTx
const getNonce = () => {
// pass through hex string or undefined
if (rawTx.nonce === undefined || isHexString(rawTx.nonce)) {
return rawTx.nonce
return rawTx.nonce as PrefixedHexString
}

// convert positive integer strings to hex, reject everything else
Expand All @@ -106,15 +108,25 @@ export function getRawTx(newTx: RPC.SendTransaction.TxParams): TransactionData {

const tx: TransactionData = {
...rawTx,
maxPriorityFeePerGas: isHexString(rawTx.maxPriorityFeePerGas)
? (rawTx.maxPriorityFeePerGas as PrefixedHexString)
: intToHex(parseInt(rawTx.maxPriorityFeePerGas || '0', 10)),
maxFeePerGas: isHexString(rawTx.maxFeePerGas)
? (rawTx.maxFeePerGas as PrefixedHexString)
: intToHex(parseInt(rawTx.maxFeePerGas || '0', 10)),
gasPrice: isHexString(rawTx.gasPrice)
? (rawTx.gasPrice as PrefixedHexString)
: intToHex(parseInt(rawTx.gasPrice || '0', 10)),
...(from && { from: getAddress(from) }),
...(to && { to: getAddress(to) }),
type: '0x0',
value: parseValue(value),
data: addHexPrefix(padToEven(stripHexPrefix(data || '0x'))),
gasLimit: gasLimit || gas,
gasLimit: (gasLimit || gas) as PrefixedHexString,
chainId: rawTx.chainId,
nonce: getNonce(),
gasFeesSource: GasFeesSource.Dapp
gasFeesSource: GasFeesSource.Dapp,
authorizationList: []
}

return tx
Expand All @@ -139,10 +151,12 @@ export function getSignedAddress(signed: string, message: string, cb: Callback<s
if (signature.length !== 65) return cb(new Error('Frame verifySignature: Signature has incorrect length'))
let v = signature[64]
v = v === 0 || v === 1 ? v + 27 : v
const r = toBuffer(signature.slice(0, 32))
const s = toBuffer(signature.slice(32, 64))
const hash = hashPersonalMessage(toBuffer(message))
const verifiedAddress = '0x' + pubToAddress(ecrecover(hash, BigInt(v), r, s)).toString('hex')
const r = Uint8Array.prototype.slice.call(signature, 0, 32)
const s = Uint8Array.prototype.slice.call(signature, 32, 64)
const hash = hashPersonalMessage(toBytes(message))
const addressBuffer = Buffer.from(pubToAddress(ecrecover(hash, BigInt(v), r, s)))
const verifiedAddress = `0x${addressBuffer.toString('hex')}`

cb(null, verifiedAddress)
}

Expand Down
18 changes: 5 additions & 13 deletions main/provider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { BigNumber } from 'ethers'
import { estimateL1GasCost } from '@eth-optimism/sdk'
import { recoverTypedSignature, SignTypedDataVersion } from '@metamask/eth-sig-util'
import { isAddress } from '@ethersproject/address'
import { addHexPrefix, intToHex, isHexString, isHexPrefixed, fromUtf8 } from '@ethereumjs/util'
import { addHexPrefix, intToHex, isHexString, fromUtf8, PrefixedHexString } from '@ethereumjs/util'

import store from '../store'
import packageFile from '../../package.json'
Expand Down Expand Up @@ -241,15 +241,7 @@ export class Provider extends EventEmitter {
const payload = req.payload
let [address, rawMessage] = payload.params

let message = rawMessage

if (isHexString(rawMessage)) {
if (!isHexPrefixed(rawMessage)) {
message = addHexPrefix(rawMessage)
}
} else {
message = fromUtf8(rawMessage)
}
const message = isHexString(rawMessage) ? rawMessage : fromUtf8(rawMessage)

accounts.signMessage(address, message, (err, signed) => {
if (err) {
Expand Down Expand Up @@ -427,7 +419,7 @@ export class Provider extends EventEmitter {
return handlerId
}

private async getGasEstimate(rawTx: TransactionData) {
private async getGasEstimate(rawTx: TransactionData): Promise<PrefixedHexString> {
const { from, to, value, data, nonce } = rawTx
const txParams = { from, to, value, data, nonce }

Expand All @@ -443,7 +435,7 @@ export class Provider extends EventEmitter {
id: parseInt(rawTx.chainId, 16)
}

return new Promise<string>((resolve, reject) => {
return new Promise<PrefixedHexString>((resolve, reject) => {
this.connection.send(
payload,
(response) => {
Expand Down Expand Up @@ -496,7 +488,7 @@ export class Provider extends EventEmitter {
const gas = gasFees(rawTx)
const { chainConfig } = connection

const estimateGasLimit = async () => {
const estimateGasLimit = async (): Promise<PrefixedHexString> => {
try {
return await this.getGasEstimate(rawTx)
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions main/signers/Signer/derive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export function deriveHDAccounts(publicKey: string, chainCode: string, cb: Callb
hdk.chainCode = Buffer.from(chainCode, 'hex')
const derive = (index: number) => {
const derivedKey = hdk.derive(`m/${index}`)
const address = publicToAddress(derivedKey.publicKey, true)
return toChecksumAddress(`0x${address.toString('hex')}`)
const address = Buffer.from(publicToAddress(derivedKey.publicKey, true)).toString('hex')
return toChecksumAddress(`0x${address}`)
}
const accounts = []
for (let i = 0; i < 100; i++) {
Expand Down
2 changes: 1 addition & 1 deletion main/signers/hot/HotSigner/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ class HotSignerWorker {
}

const chainId = parseInt(rawTx.chainId, 16)
const hardfork = parseInt(rawTx.type) === 2 ? 'london' : 'berlin'
const hardfork = parseInt(rawTx.type) === 2 ? 'london' : parseInt(rawTx.type) === 4 ? 'pectra' : 'london'
const common = chainConfig(chainId, hardfork)

const tx = TransactionFactory.fromTxData(rawTx, { common })
Expand Down
8 changes: 6 additions & 2 deletions main/signers/lattice/Lattice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,10 @@ export default class Lattice extends Signer {
}
})

cb(null, addHexPrefix(signedTx.serialize().toString('hex')))
const serializedTx = signedTx.serialize()
const txHex = addHexPrefix(Buffer.from(serializedTx).toString('hex'))

cb(null, txHex)
} catch (err) {
log.error('error signing transaction with Lattice', err)
const latticeErrorMessage = (err as LatticeResponseError).errorMessage
Expand Down Expand Up @@ -386,7 +389,8 @@ export default class Lattice extends Signer {
const fwVersion = (this.connection as Client).getFwVersion()

if (fwVersion && (fwVersion.major > 0 || fwVersion.minor >= 15)) {
const payload = tx.type ? tx.getMessageToSign(false) : encode(tx.getMessageToSign(false))
const message = tx.getMessageToSign()
const payload = tx.type ? message : encode(message)

const to = tx.to?.toString() ?? undefined

Expand Down
11 changes: 7 additions & 4 deletions main/signers/ledger/Ledger/eth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import log from 'electron-log'
import Transport from '@ledgerhq/hw-transport'
import Eth from '@ledgerhq/hw-app-eth'
import { encode } from 'rlp'
import { addHexPrefix, stripHexPrefix, padToEven } from '@ethereumjs/util'
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'
import Transport from '@ledgerhq/hw-transport'
import Eth from '@ledgerhq/hw-app-eth'

import { Derivation, getDerivationPath, deriveHDAccounts } from '../../Signer/derive'
import { sign } from '../../../transaction'
Expand Down Expand Up @@ -82,14 +82,17 @@ export default class LedgerEthereumApp {
async signTransaction(path: string, ledgerTx: TransactionData) {
const signedTx = await sign(ledgerTx, (tx) => {
// legacy transactions aren't RLP encoded before they're returned
const message = tx.getMessageToSign(false)
const message = Buffer.from(tx.getHashedMessageToSign())
const legacyMessage = message[0] !== tx.type
const rawTxHex = legacyMessage ? Buffer.from(encode(message)).toString('hex') : message.toString('hex')

return this.eth.signTransaction(path, rawTxHex, null)
})

return addHexPrefix(signedTx.serialize().toString('hex'))
const serializedTx = signedTx.serialize()
const txHex = addHexPrefix(Buffer.from(serializedTx).toString('hex'))

return txHex
}

async getAddress(path: string, display: boolean, chainCode: boolean) {
Expand Down
5 changes: 4 additions & 1 deletion main/signers/trezor/Trezor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,10 @@ export default class Trezor extends Signer {
}
})

cb(null, addHexPrefix(signedTx.serialize().toString('hex')))
const serializedTx = signedTx.serialize()
const txHex = addHexPrefix(Buffer.from(serializedTx).toString('hex'))

cb(null, txHex)
} catch (e: unknown) {
const err = e as DeviceError
cb(err)
Expand Down
Loading
Loading