1+ import type { Hex } from "ox" ;
12import type {
23 SignableMessage ,
34 TypedData ,
45 TypedDataDefinition ,
56 TypedDataDomain ,
67} from "viem" ;
8+ import { serializeErc6492Signature } from "../../../auth/serialize-erc6492-signature.js" ;
9+ import { verifyHash } from "../../../auth/verify-hash.js" ;
710import {
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" ;
1215import { readContract } from "../../../transaction/read-contract.js" ;
1316import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js" ;
14- import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js" ;
1517import { hashMessage } from "../../../utils/hashing/hashMessage.js" ;
1618import { hashTypedData } from "../../../utils/hashing/hashTypedData.js" ;
17- import type { Account } from "../../interfaces/wallet.js" ;
1819import type { SmartAccountOptions } from "../types.js" ;
20+ import { prepareCreateAccount } from "./calls.js" ;
1921
2022export async function deployAndSignMessage ( {
21- account,
2223 accountContract,
24+ factoryContract,
2325 options,
2426 message,
2527} : {
26- account : Account ;
2728 accountContract : ThirdwebContract ;
29+ factoryContract : ThirdwebContract ;
2830 options : SmartAccountOptions ;
2931 message : SignableMessage ;
3032} ) {
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-
4533 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+ } ) ;
5539
5640 let sig : `0x${string } `;
5741 if ( is712Factory ) {
@@ -75,31 +59,50 @@ export async function deployAndSignMessage({
7559 sig = await options . personalAccount . signMessage ( { message } ) ;
7660 }
7761
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 ,
8175 signature : sig ,
8276 } ) ;
8377
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+
8487 if ( isValid ) {
85- return sig ;
88+ return erc6492Sig ;
8689 }
8790 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." ,
8992 ) ;
9093}
9194
9295export async function deployAndSignTypedData <
9396 const typedData extends TypedData | Record < string , unknown > ,
9497 primaryType extends keyof typedData | "EIP712Domain" = keyof typedData ,
9598> ( {
96- account,
9799 accountContract,
100+ factoryContract,
98101 options,
99102 typedData,
100103} : {
101- account : Account ;
102104 accountContract : ThirdwebContract ;
105+ factoryContract : ThirdwebContract ;
103106 options : SmartAccountOptions ;
104107 typedData : TypedDataDefinition < typedData , primaryType > ;
105108} ) {
@@ -112,38 +115,16 @@ export async function deployAndSignTypedData<
112115 return options . personalAccount . signTypedData ( typedData ) ;
113116 }
114117
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-
129118 const originalMsgHash = hashTypedData ( typedData ) ;
130119 // 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+ } ) ;
144125
145126 let sig : `0x${string } `;
146- if ( factorySupports712 ) {
127+ if ( is712Factory ) {
147128 const wrappedMessageHash = encodeAbiParameters (
148129 [ { type : "bytes32" } ] ,
149130 [ originalMsgHash ] ,
@@ -163,46 +144,39 @@ export async function deployAndSignTypedData<
163144 sig = await options . personalAccount . signTypedData ( typedData ) ;
164145 }
165146
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
166164 const isValid = await verifyHash ( {
167165 hash : originalMsgHash ,
168- signature : sig ,
166+ signature : erc6492Sig ,
169167 address : accountContract . address ,
170- chain : options . chain ,
171- client : options . client ,
168+ chain : accountContract . chain ,
169+ client : accountContract . client ,
172170 } ) ;
173171
174172 if ( isValid ) {
175- return sig ;
173+ return erc6492Sig ;
176174 }
177175 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." ,
179177 ) ;
180178}
181179
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-
206180export async function confirmContractDeployment ( args : {
207181 accountContract : ThirdwebContract ;
208182} ) {
@@ -223,3 +197,37 @@ export async function confirmContractDeployment(args: {
223197 isDeployed = await isContractDeployed ( accountContract ) ;
224198 }
225199}
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