-
Notifications
You must be signed in to change notification settings - Fork 0
/
UserOperation.ts
205 lines (181 loc) · 8.02 KB
/
UserOperation.ts
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import { BigNumber, ethers } from 'ethers'
import { ethers as ethers2 } from 'ethers'
import {
address,
bytes,
uint256
} from "./SolidityTypes";
import Monad, { AsyncMonad } from './Monad';
import {
arrayify,
defaultAbiCoder,
hexDataSlice,
keccak256
} from 'ethers/lib/utils'
import { ecsign, toRpcSig, keccak256 as keccak256_buffer } from 'ethereumjs-util'
import { sign_hash } from 'lamportwalletmanager/src';
import KeyTrackerB from 'lamportwalletmanager/src/KeyTrackerB';
import ENTRYPOINT from './EntryPoint';
import { loadProviders } from './loaders';
import { Account } from './Account';
export interface UserOperation {
sender: address
nonce: uint256
initCode: bytes
callData: bytes
callGasLimit: uint256
verificationGasLimit: uint256
preVerificationGas: uint256
maxFeePerGas: uint256
maxPriorityFeePerGas: uint256
paymasterAndData: bytes
signature: bytes
}
export const DefaultsForUserOp: UserOperation = {
sender: ethers.constants.AddressZero,
nonce: 0,
initCode: '0x',
callData: '0x',
callGasLimit: 100_000,
verificationGasLimit: 2_000_000, // default verification gas.
preVerificationGas: 1_010_000, // should also cover calldata cost.
maxFeePerGas: 8e9,
maxPriorityFeePerGas: 8e9,
paymasterAndData: '0x',
signature: '0x'
}
export function fillUserOpDefaults(op: Partial<UserOperation>): Monad<Partial<UserOperation>> {
const defaults = DefaultsForUserOp
const partial: any = { ...op }
// we want "item:undefined" to be used from defaults, and not override defaults, so we must explicitly
// remove those so "merge" will succeed.
for (const key in partial) {
if (partial[key] == null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete partial[key]
}
}
const filled = { ...defaults, ...partial }
return Monad.of<Partial<UserOperation>>(filled)
}
export function fillUserOpDefaultsAsync(op: Partial<UserOperation>): AsyncMonad<Partial<UserOperation>> {
const defaults = DefaultsForUserOp
const partial: any = { ...op }
// we want "item:undefined" to be used from defaults, and not override defaults, so we must explicitly
// remove those so "merge" will succeed.
for (const key in partial) {
if (partial[key] == null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete partial[key]
}
}
const filled = { ...defaults, ...partial }
return AsyncMonad.of<Partial<UserOperation>>(filled)
}
export function packUserOp(op: UserOperation, forSignature = true): string {
if (forSignature) {
return defaultAbiCoder.encode(
['address', 'uint256', 'bytes32', 'bytes32',
'uint256', 'uint256', 'uint256', 'uint256', 'uint256',
'bytes32'],
[op.sender, op.nonce, keccak256(op.initCode), keccak256(op.callData),
op.callGasLimit, op.verificationGasLimit, op.preVerificationGas, op.maxFeePerGas, op.maxPriorityFeePerGas,
keccak256(op.paymasterAndData)])
} else {
// for the purpose of calculating gas cost encode also signature (and no keccak of bytes)
return defaultAbiCoder.encode(
['address', 'uint256', 'bytes', 'bytes',
'uint256', 'uint256', 'uint256', 'uint256', 'uint256',
'bytes', 'bytes'],
[op.sender, op.nonce, op.initCode, op.callData,
op.callGasLimit, op.verificationGasLimit, op.preVerificationGas, op.maxFeePerGas, op.maxPriorityFeePerGas,
op.paymasterAndData, op.signature])
}
}
export function getUserOpHash(op: UserOperation, entryPoint: string, chainId: number): string {
const userOpHash = keccak256(packUserOp(op, true))
const enc = defaultAbiCoder.encode(
['bytes32', 'address', 'uint256'],
[userOpHash, entryPoint, chainId])
const temp = keccak256(enc)
return temp
}
export function ecdsaSign(op: UserOperation, signer: ethers2.Wallet, entryPoint: string, chainId: number): string {
const message = getUserOpHash(op, entryPoint, chainId)
const msg1 = Buffer.concat([
Buffer.from('\x19Ethereum Signed Message:\n32', 'ascii'),
Buffer.from(arrayify(message))
])
const sig = ecsign(keccak256_buffer(msg1), Buffer.from(arrayify(signer.privateKey)))
// that's equivalent of: await signer.signMessage(message);
// (but without "async"
const signedMessage1 = toRpcSig(sig.v, sig.r, sig.s)
return signedMessage1
}
const show = (value: any) => {
console.log(`value: `, value)
return Monad.of(value)
}
const lamportSignUserOp = (op: UserOperation, signer: ethers2.Wallet, entryPoint: string, chainId: number, keys: KeyTrackerB): Monad<UserOperation> => {
const ecdsaSig = ecdsaSign(op, signer, entryPoint, chainId)
const message = getUserOpHash(op, entryPoint, chainId)
const message2 = ethers.utils.hashMessage(ethers.utils.arrayify(message))
const signingKeys = keys.getOne()
const signature = sign_hash(message2, signingKeys.pri)
const packedSignature = ethers.utils.defaultAbiCoder.encode(['bytes[256]', 'bytes32[2][256]', 'bytes'], [signature, signingKeys.pub, ecdsaSig])
return Monad.of({
...op,
signature: packedSignature
} as UserOperation)
}
const lamportSignUserOpAsync = (op: UserOperation, signer: ethers2.Wallet, entryPoint: string, chainId: number, keys: KeyTrackerB): AsyncMonad<UserOperation> => {
const ecdsaSig = ecdsaSign(op, signer, entryPoint, chainId)
const message = getUserOpHash(op, entryPoint, chainId)
const message2 = ethers.utils.hashMessage(ethers.utils.arrayify(message))
const signingKeys = keys.getOne()
const signature = sign_hash(message2, signingKeys.pri)
const packedSignature = ethers.utils.defaultAbiCoder.encode(['bytes[256]', 'bytes32[2][256]', 'bytes'], [signature, signingKeys.pub, ecdsaSig])
return AsyncMonad.of({
...op,
signature: packedSignature
} as UserOperation)
}
const gasMult = (multiplier: number) => (op: Partial<UserOperation>) => AsyncMonad.of({
...op,
callGasLimit: BigNumber.from(op.callGasLimit).mul(multiplier),
// verificationGasLimit: BigNumber.from(op.verificationGasLimit).mul(multiplier),
// preVerificationGas: BigNumber.from(op.preVerificationGas).mul(multiplier),
} as UserOperation)
const estimateGas = async (account: Account, op: Partial<UserOperation>): Promise<AsyncMonad<UserOperation>> => {
// create a mock signature for our estimate
const opWithFakeSignature = JSON.parse(JSON.stringify(op)) as UserOperation
const signer = ethers.Wallet.fromMnemonic(account.ecdsaSecret, account.ecdsaPath)
const ecdsaSig = ecdsaSign(op as UserOperation, signer, ENTRYPOINT, account.network)
const message = getUserOpHash(op as UserOperation, ENTRYPOINT, account.network)
const message2 = ethers.utils.hashMessage(ethers.utils.arrayify(message))
const notMyKeys = new KeyTrackerB() // can't use our real keys (OTS) because signed object will change after we have gas estimates
notMyKeys.more(1)
const signingKeys = notMyKeys.getOne()
const signature = sign_hash(message2, signingKeys.pri)
const packedSignature = ethers.utils.defaultAbiCoder.encode(['bytes[256]', 'bytes32[2][256]', 'bytes'], [signature, signingKeys.pub, ecdsaSig])
opWithFakeSignature.signature = packedSignature
const [normalProvider, bundlerProvider] = loadProviders(account.chainName)
const est = await bundlerProvider.send('eth_estimateUserOperationGas', [opWithFakeSignature, ENTRYPOINT])
console.log(`Estimate gas: `, est)
op.callGasLimit = est.callGasLimit
op.preVerificationGas = est.preVerificationGas
op.verificationGasLimit = est.verificationGas
return AsyncMonad.of(op as UserOperation)
}
const stub = (msgDisplay : (op : UserOperation) => void) => (op : UserOperation) => {
msgDisplay(op)
return AsyncMonad.of(op as UserOperation)
}
export {
show,
lamportSignUserOp,
lamportSignUserOpAsync,
estimateGas,
gasMult ,
stub,
}