1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/* External Imports */
import { ethers } from '@nomiclabs/buidler'
import { zeroPad } from '@ethersproject/bytes'
import { Wallet } from 'ethers'
import {
remove0x,
numberToHexString,
hexStrToBuf,
makeAddressManager,
} from '../'
import { ZERO_ADDRESS } from '../constants'
export interface EIP155Transaction {
nonce: number
gasLimit: number
gasPrice: number
to: string
data: string
chainId: number
}
export interface SignatureParameters {
messageHash: string
v: string
r: string
s: string
}
export const DEFAULT_EIP155_TX: EIP155Transaction = {
to: `0x${'12'.repeat(20)}`,
nonce: 100,
gasLimit: 1000000,
gasPrice: 100000000,
data: `0x${'99'.repeat(10)}`,
chainId: 422,
}
export const getRawSignedComponents = (signed: string): any[] => {
return [signed.slice(130, 132), signed.slice(2, 66), signed.slice(66, 130)]
}
export const getSignedComponents = (signed: string): any[] => {
return ethers.utils.RLP.decode(signed).slice(-3)
}
export const encodeCompactTransaction = (transaction: any): string => {
const nonce = zeroPad(transaction.nonce, 3)
const gasLimit = zeroPad(transaction.gasLimit, 3)
if (transaction.gasPrice % 1000000 !== 0)
throw Error('gas price must be a multiple of 1000000')
const compressedGasPrice: any = transaction.gasPrice / 1000000
const gasPrice = zeroPad(compressedGasPrice, 3)
const to = !transaction.to.length
? hexStrToBuf(ZERO_ADDRESS)
: hexStrToBuf(transaction.to)
const data = hexStrToBuf(transaction.data)
return Buffer.concat([
Buffer.from(gasLimit),
Buffer.from(gasPrice),
Buffer.from(nonce),
Buffer.from(to),
data,
]).toString('hex')
}
export const serializeEthSignTransaction = (
transaction: EIP155Transaction
): string => {
return ethers.utils.defaultAbiCoder.encode(
['uint256', 'uint256', 'uint256', 'uint256', 'address', 'bytes'],
[
transaction.nonce,
transaction.gasLimit,
transaction.gasPrice,
transaction.chainId,
transaction.to,
transaction.data,
]
)
}
export const serializeNativeTransaction = (
transaction: EIP155Transaction
): string => {
return ethers.utils.serializeTransaction(transaction)
}
export const signEthSignMessage = async (
wallet: Wallet,
transaction: EIP155Transaction
): Promise<SignatureParameters> => {
const serializedTransaction = serializeEthSignTransaction(transaction)
const transactionHash = ethers.utils.keccak256(serializedTransaction)
const transactionHashBytes = ethers.utils.arrayify(transactionHash)
const transactionSignature = await wallet.signMessage(transactionHashBytes)
const messageHash = ethers.utils.hashMessage(transactionHashBytes)
let [v, r, s] = getRawSignedComponents(transactionSignature).map(
(component) => {
return remove0x(component)
}
)
v = '0' + (parseInt(v, 16) - 27)
return {
messageHash,
v,
r,
s,
}
}
export const signNativeTransaction = async (
wallet: Wallet,
transaction: EIP155Transaction
): Promise<SignatureParameters> => {
const serializedTransaction = serializeNativeTransaction(transaction)
const transactionSignature = await wallet.signTransaction(transaction)
const messageHash = ethers.utils.keccak256(serializedTransaction)
let [v, r, s] = getSignedComponents(transactionSignature).map((component) => {
return remove0x(component)
})
v = '0' + (parseInt(v, 16) - transaction.chainId * 2 - 8 - 27)
return {
messageHash,
v,
r,
s,
}
}
export const signTransaction = async (
wallet: Wallet,
transaction: EIP155Transaction,
transactionType: number
): Promise<SignatureParameters> => {
return transactionType === 2
? signEthSignMessage(wallet, transaction) //ETH Signed tx
: signNativeTransaction(wallet, transaction) //Create EOA tx or EIP155 tx
}
export const encodeSequencerCalldata = async (
wallet: Wallet,
transaction: EIP155Transaction,
transactionType: number
) => {
const sig = await signTransaction(wallet, transaction, transactionType)
const encodedTransaction = encodeCompactTransaction(transaction)
const dataPrefix = `0x0${transactionType}${sig.r}${sig.s}${sig.v}`
const calldata =
transactionType === 1
? `${dataPrefix}${remove0x(sig.messageHash)}` // Create EOA tx
: `${dataPrefix}${encodedTransaction}` // EIP155 tx or ETH Signed Tx
return calldata
}