|
1 | 1 | import type * as ox__TypedData from "ox/TypedData"; |
2 | 2 | import { serializeErc6492Signature } from "../../../auth/serialize-erc6492-signature.js"; |
3 | | -import { verifyHash } from "../../../auth/verify-hash.js"; |
| 3 | +import { |
| 4 | + verifyEip1271Signature, |
| 5 | + verifyHash, |
| 6 | +} from "../../../auth/verify-hash.js"; |
| 7 | +import type { Chain } from "../../../chains/types.js"; |
| 8 | +import type { ThirdwebClient } from "../../../client/client.js"; |
4 | 9 | import { |
5 | 10 | type ThirdwebContract, |
6 | 11 | getContract, |
7 | 12 | } from "../../../contract/contract.js"; |
8 | 13 | import { encode } from "../../../transaction/actions/encode.js"; |
9 | 14 | import { readContract } from "../../../transaction/read-contract.js"; |
10 | 15 | import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js"; |
| 16 | +import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js"; |
11 | 17 | import type { Hex } from "../../../utils/encoding/hex.js"; |
12 | 18 | import { hashMessage } from "../../../utils/hashing/hashMessage.js"; |
13 | 19 | import { hashTypedData } from "../../../utils/hashing/hashTypedData.js"; |
14 | 20 | import type { SignableMessage } from "../../../utils/types.js"; |
| 21 | +import type { Account } from "../../../wallets/interfaces/wallet.js"; |
15 | 22 | import type { SmartAccountOptions } from "../types.js"; |
16 | 23 | import { prepareCreateAccount } from "./calls.js"; |
17 | 24 |
|
18 | | -export async function deployAndSignMessage({ |
| 25 | +/** |
| 26 | + * If the account is already deployed, generate an ERC-1271 signature. |
| 27 | + * If the account is not deployed, generate an ERC-6492 signature unless otherwise specified. |
| 28 | + * |
| 29 | + * @internal |
| 30 | + */ |
| 31 | +export async function smartAccountSignMessage({ |
| 32 | + smartAccount, |
19 | 33 | accountContract, |
20 | 34 | factoryContract, |
21 | 35 | options, |
22 | 36 | message, |
23 | 37 | }: { |
| 38 | + smartAccount: Account; |
24 | 39 | accountContract: ThirdwebContract; |
25 | 40 | factoryContract: ThirdwebContract; |
26 | 41 | options: SmartAccountOptions; |
@@ -55,48 +70,73 @@ export async function deployAndSignMessage({ |
55 | 70 | sig = await options.personalAccount.signMessage({ message }); |
56 | 71 | } |
57 | 72 |
|
58 | | - const deployTx = prepareCreateAccount({ |
59 | | - factoryContract, |
60 | | - adminAddress: options.personalAccount.address, |
61 | | - accountSalt: options.overrides?.accountSalt, |
62 | | - createAccountOverride: options.overrides?.createAccount, |
63 | | - }); |
64 | | - if (!deployTx) { |
65 | | - throw new Error("Create account override not provided"); |
| 73 | + if (options.eagerDeployment) { |
| 74 | + await forceDeploySmartAccount({ |
| 75 | + chain: options.chain, |
| 76 | + client: options.client, |
| 77 | + smartAccount, |
| 78 | + accountContract, |
| 79 | + }); |
| 80 | + await confirmContractDeployment({ |
| 81 | + accountContract, |
| 82 | + }); |
66 | 83 | } |
67 | | - const initCode = await encode(deployTx); |
68 | | - const erc6492Sig = serializeErc6492Signature({ |
69 | | - address: factoryContract.address, |
70 | | - data: initCode, |
71 | | - signature: sig, |
72 | | - }); |
73 | 84 |
|
74 | | - // check if the signature is valid |
75 | | - const isValid = await verifyHash({ |
76 | | - hash: originalMsgHash, |
77 | | - signature: erc6492Sig, |
78 | | - address: accountContract.address, |
79 | | - chain: accountContract.chain, |
80 | | - client: accountContract.client, |
81 | | - }); |
| 85 | + const isDeployed = await isContractDeployed(accountContract); |
| 86 | + if (isDeployed) { |
| 87 | + const isValid = await verifyEip1271Signature({ |
| 88 | + hash: originalMsgHash, |
| 89 | + signature: sig, |
| 90 | + contract: accountContract, |
| 91 | + }); |
| 92 | + if (isValid) { |
| 93 | + return sig; |
| 94 | + } |
| 95 | + throw new Error("Failed to verify signature"); |
| 96 | + } else { |
| 97 | + const deployTx = prepareCreateAccount({ |
| 98 | + factoryContract, |
| 99 | + adminAddress: options.personalAccount.address, |
| 100 | + accountSalt: options.overrides?.accountSalt, |
| 101 | + createAccountOverride: options.overrides?.createAccount, |
| 102 | + }); |
| 103 | + if (!deployTx) { |
| 104 | + throw new Error("Create account override not provided"); |
| 105 | + } |
| 106 | + const initCode = await encode(deployTx); |
| 107 | + const erc6492Sig = serializeErc6492Signature({ |
| 108 | + address: factoryContract.address, |
| 109 | + data: initCode, |
| 110 | + signature: sig, |
| 111 | + }); |
82 | 112 |
|
83 | | - if (isValid) { |
84 | | - return erc6492Sig; |
| 113 | + // check if the signature is valid |
| 114 | + const isValid = await verifyHash({ |
| 115 | + hash: originalMsgHash, |
| 116 | + signature: erc6492Sig, |
| 117 | + address: accountContract.address, |
| 118 | + chain: accountContract.chain, |
| 119 | + client: accountContract.client, |
| 120 | + }); |
| 121 | + |
| 122 | + if (isValid) { |
| 123 | + return erc6492Sig; |
| 124 | + } |
| 125 | + throw new Error("Unable to verify ERC-6492 signature after signing."); |
85 | 126 | } |
86 | | - throw new Error( |
87 | | - "Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid.", |
88 | | - ); |
89 | 127 | } |
90 | 128 |
|
91 | | -export async function deployAndSignTypedData< |
| 129 | +export async function smartAccountSignTypedData< |
92 | 130 | const typedData extends ox__TypedData.TypedData | Record<string, unknown>, |
93 | 131 | primaryType extends keyof typedData | "EIP712Domain" = keyof typedData, |
94 | 132 | >({ |
| 133 | + smartAccount, |
95 | 134 | accountContract, |
96 | 135 | factoryContract, |
97 | 136 | options, |
98 | 137 | typedData, |
99 | 138 | }: { |
| 139 | + smartAccount: Account; |
100 | 140 | accountContract: ThirdwebContract; |
101 | 141 | factoryContract: ThirdwebContract; |
102 | 142 | options: SmartAccountOptions; |
@@ -142,37 +182,62 @@ export async function deployAndSignTypedData< |
142 | 182 | sig = await options.personalAccount.signTypedData(typedData); |
143 | 183 | } |
144 | 184 |
|
145 | | - const deployTx = prepareCreateAccount({ |
146 | | - factoryContract, |
147 | | - adminAddress: options.personalAccount.address, |
148 | | - accountSalt: options.overrides?.accountSalt, |
149 | | - createAccountOverride: options.overrides?.createAccount, |
150 | | - }); |
151 | | - if (!deployTx) { |
152 | | - throw new Error("Create account override not provided"); |
| 185 | + if (options.eagerDeployment) { |
| 186 | + await forceDeploySmartAccount({ |
| 187 | + chain: options.chain, |
| 188 | + client: options.client, |
| 189 | + smartAccount, |
| 190 | + accountContract, |
| 191 | + }); |
| 192 | + await confirmContractDeployment({ |
| 193 | + accountContract, |
| 194 | + }); |
153 | 195 | } |
154 | | - const initCode = await encode(deployTx); |
155 | | - const erc6492Sig = serializeErc6492Signature({ |
156 | | - address: factoryContract.address, |
157 | | - data: initCode, |
158 | | - signature: sig, |
159 | | - }); |
160 | 196 |
|
161 | | - // check if the signature is valid |
162 | | - const isValid = await verifyHash({ |
163 | | - hash: originalMsgHash, |
164 | | - signature: erc6492Sig, |
165 | | - address: accountContract.address, |
166 | | - chain: accountContract.chain, |
167 | | - client: accountContract.client, |
168 | | - }); |
| 197 | + const isDeployed = await isContractDeployed(accountContract); |
| 198 | + if (isDeployed) { |
| 199 | + const isValid = await verifyEip1271Signature({ |
| 200 | + hash: originalMsgHash, |
| 201 | + signature: sig, |
| 202 | + contract: accountContract, |
| 203 | + }); |
| 204 | + if (isValid) { |
| 205 | + return sig; |
| 206 | + } |
| 207 | + throw new Error("Failed to verify signature"); |
| 208 | + } else { |
| 209 | + const deployTx = prepareCreateAccount({ |
| 210 | + factoryContract, |
| 211 | + adminAddress: options.personalAccount.address, |
| 212 | + accountSalt: options.overrides?.accountSalt, |
| 213 | + createAccountOverride: options.overrides?.createAccount, |
| 214 | + }); |
| 215 | + if (!deployTx) { |
| 216 | + throw new Error("Create account override not provided"); |
| 217 | + } |
| 218 | + const initCode = await encode(deployTx); |
| 219 | + const erc6492Sig = serializeErc6492Signature({ |
| 220 | + address: factoryContract.address, |
| 221 | + data: initCode, |
| 222 | + signature: sig, |
| 223 | + }); |
169 | 224 |
|
170 | | - if (isValid) { |
171 | | - return erc6492Sig; |
| 225 | + // check if the signature is valid |
| 226 | + const isValid = await verifyHash({ |
| 227 | + hash: originalMsgHash, |
| 228 | + signature: erc6492Sig, |
| 229 | + address: accountContract.address, |
| 230 | + chain: accountContract.chain, |
| 231 | + client: accountContract.client, |
| 232 | + }); |
| 233 | + |
| 234 | + if (isValid) { |
| 235 | + return erc6492Sig; |
| 236 | + } |
| 237 | + throw new Error( |
| 238 | + "Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid.", |
| 239 | + ); |
172 | 240 | } |
173 | | - throw new Error( |
174 | | - "Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid.", |
175 | | - ); |
176 | 241 | } |
177 | 242 |
|
178 | 243 | export async function confirmContractDeployment(args: { |
@@ -229,3 +294,38 @@ async function checkFor712Factory({ |
229 | 294 | return false; |
230 | 295 | } |
231 | 296 | } |
| 297 | + |
| 298 | +/** |
| 299 | + * Forces smart account deployment via a dummy transaction |
| 300 | + * |
| 301 | + * @internal |
| 302 | + */ |
| 303 | +export async function forceDeploySmartAccount(args: { |
| 304 | + smartAccount: Account; |
| 305 | + chain: Chain; |
| 306 | + client: ThirdwebClient; |
| 307 | + accountContract: ThirdwebContract; |
| 308 | +}) { |
| 309 | + const { chain, client, smartAccount, accountContract } = args; |
| 310 | + const isDeployed = await isContractDeployed(accountContract); |
| 311 | + if (isDeployed) { |
| 312 | + return; |
| 313 | + } |
| 314 | + |
| 315 | + const [{ sendTransaction }, { prepareTransaction }] = await Promise.all([ |
| 316 | + import("../../../transaction/actions/send-transaction.js"), |
| 317 | + import("../../../transaction/prepare-transaction.js"), |
| 318 | + ]); |
| 319 | + const dummyTx = prepareTransaction({ |
| 320 | + client: client, |
| 321 | + chain: chain, |
| 322 | + to: accountContract.address, |
| 323 | + value: 0n, |
| 324 | + gas: 50000n, // force gas to avoid simulation error |
| 325 | + }); |
| 326 | + const deployResult = await sendTransaction({ |
| 327 | + transaction: dummyTx, |
| 328 | + account: smartAccount, |
| 329 | + }); |
| 330 | + return deployResult; |
| 331 | +} |
0 commit comments