1
+ import type { Hex } from "ox" ;
1
2
import type {
2
3
SignableMessage ,
3
4
TypedData ,
4
5
TypedDataDefinition ,
5
6
TypedDataDomain ,
6
7
} from "viem" ;
8
+ import { serializeErc6492Signature } from "../../../auth/serialize-erc6492-signature.js" ;
9
+ import { verifyHash } from "../../../auth/verify-hash.js" ;
7
10
import {
8
- verifyEip1271Signature ,
9
- verifyHash ,
10
- } from "../../../auth/verify-hash .js" ;
11
- import type { ThirdwebContract } from "../../../contract/contract .js" ;
11
+ type ThirdwebContract ,
12
+ getContract ,
13
+ } from "../../../contract/contract .js" ;
14
+ import { encode } from "../../../transaction/actions/encode .js" ;
12
15
import { readContract } from "../../../transaction/read-contract.js" ;
13
16
import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js" ;
14
- import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js" ;
15
17
import { hashMessage } from "../../../utils/hashing/hashMessage.js" ;
16
18
import { hashTypedData } from "../../../utils/hashing/hashTypedData.js" ;
17
- import type { Account } from "../../interfaces/wallet.js" ;
18
19
import type { SmartAccountOptions } from "../types.js" ;
20
+ import { prepareCreateAccount } from "./calls.js" ;
19
21
20
22
export async function deployAndSignMessage ( {
21
- account,
22
23
accountContract,
24
+ factoryContract,
23
25
options,
24
26
message,
25
27
} : {
26
- account : Account ;
27
28
accountContract : ThirdwebContract ;
29
+ factoryContract : ThirdwebContract ;
28
30
options : SmartAccountOptions ;
29
31
message : SignableMessage ;
30
32
} ) {
31
- const isDeployed = await isContractDeployed ( accountContract ) ;
32
- if ( ! isDeployed ) {
33
- await _deployAccount ( {
34
- options,
35
- account,
36
- accountContract,
37
- } ) ;
38
- // the bundler and rpc might not be in sync, so while the bundler has a transaction hash for the deployment,
39
- // the rpc might not have it yet, so we wait until the rpc confirms the contract is deployed
40
- await confirmContractDeployment ( {
41
- accountContract,
42
- } ) ;
43
- }
44
-
45
33
const originalMsgHash = hashMessage ( message ) ;
46
- // check if the account contract supports EIP721 domain separator or modular based signing
47
- const is712Factory = await readContract ( {
48
- contract : accountContract ,
49
- method :
50
- "function getMessageHash(bytes32 _hash) public view returns (bytes32)" ,
51
- params : [ originalMsgHash ] ,
52
- } )
53
- . then ( ( res ) => res !== "0x" )
54
- . catch ( ( ) => false ) ;
34
+ const is712Factory = await checkFor712Factory ( {
35
+ factoryContract,
36
+ accountContract,
37
+ originalMsgHash,
38
+ } ) ;
55
39
56
40
let sig : `0x${string } `;
57
41
if ( is712Factory ) {
@@ -75,31 +59,50 @@ export async function deployAndSignMessage({
75
59
sig = await options . personalAccount . signMessage ( { message } ) ;
76
60
}
77
61
78
- const isValid = await verifyEip1271Signature ( {
79
- contract : accountContract ,
80
- hash : originalMsgHash ,
62
+ const deployTx = prepareCreateAccount ( {
63
+ factoryContract,
64
+ adminAddress : options . personalAccount . address ,
65
+ accountSalt : options . overrides ?. accountSalt ,
66
+ createAccountOverride : options . overrides ?. createAccount ,
67
+ } ) ;
68
+ if ( ! deployTx ) {
69
+ throw new Error ( "Create account override not provided" ) ;
70
+ }
71
+ const initCode = await encode ( deployTx ) ;
72
+ const erc6492Sig = serializeErc6492Signature ( {
73
+ address : factoryContract . address ,
74
+ data : initCode ,
81
75
signature : sig ,
82
76
} ) ;
83
77
78
+ // check if the signature is valid
79
+ const isValid = await verifyHash ( {
80
+ hash : originalMsgHash ,
81
+ signature : erc6492Sig ,
82
+ address : accountContract . address ,
83
+ chain : accountContract . chain ,
84
+ client : accountContract . client ,
85
+ } ) ;
86
+
84
87
if ( isValid ) {
85
- return sig ;
88
+ return erc6492Sig ;
86
89
}
87
90
throw new Error (
88
- "Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid." ,
91
+ "Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid." ,
89
92
) ;
90
93
}
91
94
92
95
export async function deployAndSignTypedData <
93
96
const typedData extends TypedData | Record < string , unknown > ,
94
97
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData ,
95
98
> ( {
96
- account,
97
99
accountContract,
100
+ factoryContract,
98
101
options,
99
102
typedData,
100
103
} : {
101
- account : Account ;
102
104
accountContract : ThirdwebContract ;
105
+ factoryContract : ThirdwebContract ;
103
106
options : SmartAccountOptions ;
104
107
typedData : TypedDataDefinition < typedData , primaryType > ;
105
108
} ) {
@@ -112,38 +115,16 @@ export async function deployAndSignTypedData<
112
115
return options . personalAccount . signTypedData ( typedData ) ;
113
116
}
114
117
115
- const isDeployed = await isContractDeployed ( accountContract ) ;
116
- if ( ! isDeployed ) {
117
- await _deployAccount ( {
118
- options,
119
- account,
120
- accountContract,
121
- } ) ;
122
- // the bundler and rpc might not be in sync, so while the bundler has a transaction hash for the deployment,
123
- // the rpc might not have it yet, so we wait until the rpc confirms the contract is deployed
124
- await confirmContractDeployment ( {
125
- accountContract,
126
- } ) ;
127
- }
128
-
129
118
const originalMsgHash = hashTypedData ( typedData ) ;
130
119
// check if the account contract supports EIP721 domain separator based signing
131
- let factorySupports712 = false ;
132
- try {
133
- // this will throw if the contract does not support it (old factories)
134
- await readContract ( {
135
- contract : accountContract ,
136
- method :
137
- "function getMessageHash(bytes32 _hash) public view returns (bytes32)" ,
138
- params : [ originalMsgHash ] ,
139
- } ) ;
140
- factorySupports712 = true ;
141
- } catch {
142
- // ignore
143
- }
120
+ const is712Factory = await checkFor712Factory ( {
121
+ factoryContract,
122
+ accountContract,
123
+ originalMsgHash,
124
+ } ) ;
144
125
145
126
let sig : `0x${string } `;
146
- if ( factorySupports712 ) {
127
+ if ( is712Factory ) {
147
128
const wrappedMessageHash = encodeAbiParameters (
148
129
[ { type : "bytes32" } ] ,
149
130
[ originalMsgHash ] ,
@@ -163,46 +144,39 @@ export async function deployAndSignTypedData<
163
144
sig = await options . personalAccount . signTypedData ( typedData ) ;
164
145
}
165
146
147
+ const deployTx = prepareCreateAccount ( {
148
+ factoryContract,
149
+ adminAddress : options . personalAccount . address ,
150
+ accountSalt : options . overrides ?. accountSalt ,
151
+ createAccountOverride : options . overrides ?. createAccount ,
152
+ } ) ;
153
+ if ( ! deployTx ) {
154
+ throw new Error ( "Create account override not provided" ) ;
155
+ }
156
+ const initCode = await encode ( deployTx ) ;
157
+ const erc6492Sig = serializeErc6492Signature ( {
158
+ address : factoryContract . address ,
159
+ data : initCode ,
160
+ signature : sig ,
161
+ } ) ;
162
+
163
+ // check if the signature is valid
166
164
const isValid = await verifyHash ( {
167
165
hash : originalMsgHash ,
168
- signature : sig ,
166
+ signature : erc6492Sig ,
169
167
address : accountContract . address ,
170
- chain : options . chain ,
171
- client : options . client ,
168
+ chain : accountContract . chain ,
169
+ client : accountContract . client ,
172
170
} ) ;
173
171
174
172
if ( isValid ) {
175
- return sig ;
173
+ return erc6492Sig ;
176
174
}
177
175
throw new Error (
178
- "Unable to verify signature on smart account, please make sure the smart account is deployed and the signature is valid." ,
176
+ "Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid." ,
179
177
) ;
180
178
}
181
179
182
- async function _deployAccount ( args : {
183
- options : SmartAccountOptions ;
184
- account : Account ;
185
- accountContract : ThirdwebContract ;
186
- } ) {
187
- const { options, account, accountContract } = args ;
188
- const [ { sendTransaction } , { prepareTransaction } ] = await Promise . all ( [
189
- import ( "../../../transaction/actions/send-transaction.js" ) ,
190
- import ( "../../../transaction/prepare-transaction.js" ) ,
191
- ] ) ;
192
- const dummyTx = prepareTransaction ( {
193
- client : options . client ,
194
- chain : options . chain ,
195
- to : accountContract . address ,
196
- value : 0n ,
197
- gas : 50000n , // force gas to avoid simulation error
198
- } ) ;
199
- const deployResult = await sendTransaction ( {
200
- transaction : dummyTx ,
201
- account,
202
- } ) ;
203
- return deployResult ;
204
- }
205
-
206
180
export async function confirmContractDeployment ( args : {
207
181
accountContract : ThirdwebContract ;
208
182
} ) {
@@ -223,3 +197,37 @@ export async function confirmContractDeployment(args: {
223
197
isDeployed = await isContractDeployed ( accountContract ) ;
224
198
}
225
199
}
200
+
201
+ async function checkFor712Factory ( {
202
+ factoryContract,
203
+ accountContract,
204
+ originalMsgHash,
205
+ } : {
206
+ factoryContract : ThirdwebContract ;
207
+ accountContract : ThirdwebContract ;
208
+ originalMsgHash : Hex . Hex ;
209
+ } ) {
210
+ try {
211
+ const implementationAccount = await readContract ( {
212
+ contract : factoryContract ,
213
+ method : "function accountImplementation() public view returns (address)" ,
214
+ } ) ;
215
+ // check if the account contract supports EIP721 domain separator or modular based signing
216
+ const is712Factory = await readContract ( {
217
+ contract : getContract ( {
218
+ address : implementationAccount ,
219
+ chain : accountContract . chain ,
220
+ client : accountContract . client ,
221
+ } ) ,
222
+ method :
223
+ "function getMessageHash(bytes32 _hash) public view returns (bytes32)" ,
224
+ params : [ originalMsgHash ] ,
225
+ } )
226
+ . then ( ( res ) => res !== "0x" )
227
+ . catch ( ( ) => false ) ;
228
+
229
+ return is712Factory ;
230
+ } catch {
231
+ return false ;
232
+ }
233
+ }
0 commit comments