Skip to content

Commit f4678cd

Browse files
committed
fix: Adds 1271 signatures and force deployment function
1 parent 7293dfa commit f4678cd

File tree

9 files changed

+302
-74
lines changed

9 files changed

+302
-74
lines changed

.changeset/polite-trains-kick.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Feature: Adds `deploySmartAccount` function to force the deployment of a smart account.
6+
7+
```ts
8+
const account = await deploySmartAccount({
9+
smartAccount,
10+
chain,
11+
client,
12+
accountContract,
13+
});
14+
```
15+
16+
Fix: Uses 1271 signatures if the smart account is already deployed.

packages/thirdweb/src/auth/verify-hash.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,7 @@ export async function verifyHash({
129129
try {
130130
const result = await eth_call(rpcRequest, verificationData);
131131
return hexToBool(result);
132-
} catch (err) {
133-
console.error("Error verifying ERC-6492 signature", err);
132+
} catch {
134133
// Some chains do not support the eth_call simulation and will fail, so we fall back to regular EIP1271 validation
135134
const validEip1271 = await verifyEip1271Signature({
136135
hash,
@@ -154,7 +153,7 @@ export async function verifyHash({
154153
}
155154

156155
const EIP_1271_MAGIC_VALUE = "0x1626ba7e";
157-
async function verifyEip1271Signature({
156+
export async function verifyEip1271Signature({
158157
hash,
159158
signature,
160159
contract,
@@ -163,10 +162,14 @@ async function verifyEip1271Signature({
163162
signature: Hex;
164163
contract: ThirdwebContract;
165164
}): Promise<boolean> {
166-
const result = await isValidSignature({
167-
hash,
168-
signature,
169-
contract,
170-
});
171-
return result === EIP_1271_MAGIC_VALUE;
165+
try {
166+
const result = await isValidSignature({
167+
hash,
168+
signature,
169+
contract,
170+
});
171+
return result === EIP_1271_MAGIC_VALUE;
172+
} catch {
173+
return false;
174+
}
172175
}

packages/thirdweb/src/exports/thirdweb.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,5 @@ export {
302302
type VerifyTypedDataParams,
303303
verifyTypedData,
304304
} from "../auth/verify-typed-data.js";
305+
306+
export { deploySmartAccount } from "../wallets/smart/lib/signing.js";

packages/thirdweb/src/exports/wallets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,5 @@ export * as EIP1193 from "../adapters/eip1193/index.js";
160160
export { injectedProvider } from "../wallets/injected/mipdStore.js";
161161

162162
export type { ConnectionManager } from "../wallets/manager/index.js";
163+
164+
export { deploySmartAccount } from "../wallets/smart/lib/signing.js";

packages/thirdweb/src/wallets/create-wallet.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,22 @@ import { createWalletEmitter } from "./wallet-emitter.js";
116116
*
117117
* [View Coinbase wallet creation options](https://portal.thirdweb.com/references/typescript/v5/CoinbaseWalletCreationOptions)
118118
*
119+
* ## Connecting with a smart wallet
120+
*
121+
* ```ts
122+
* import { createWallet } from "thirdweb/wallets";
123+
*
124+
* const wallet = createWallet("smart", {
125+
* chain: sepolia,
126+
* sponsorGas: true,
127+
* });
128+
*
129+
* const account = await wallet.connect({
130+
* client,
131+
* personalAccount, // pass the admin account
132+
* });
133+
* ```
134+
*
119135
* @wallet
120136
*/
121137
export function createWallet<const ID extends WalletId>(

packages/thirdweb/src/wallets/smart/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ async function createSmartAccount(
277277
});
278278
}
279279

280-
const { deployAndSignMessage } = await import("./lib/signing.js");
281-
return deployAndSignMessage({
280+
const { smartAccountSignMessage } = await import("./lib/signing.js");
281+
return smartAccountSignMessage({
282282
accountContract,
283283
factoryContract: options.factoryContract,
284284
options,
@@ -298,8 +298,8 @@ async function createSmartAccount(
298298
});
299299
}
300300

301-
const { deployAndSignTypedData } = await import("./lib/signing.js");
302-
return deployAndSignTypedData({
301+
const { smartAccountSignTypedData } = await import("./lib/signing.js");
302+
return smartAccountSignTypedData({
303303
accountContract,
304304
factoryContract: options.factoryContract,
305305
options,

packages/thirdweb/src/wallets/smart/lib/signing.ts

Lines changed: 154 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
import type * as ox__TypedData from "ox/TypedData";
22
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";
49
import {
510
type ThirdwebContract,
611
getContract,
712
} from "../../../contract/contract.js";
813
import { encode } from "../../../transaction/actions/encode.js";
914
import { readContract } from "../../../transaction/read-contract.js";
1015
import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js";
16+
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
1117
import type { Hex } from "../../../utils/encoding/hex.js";
1218
import { hashMessage } from "../../../utils/hashing/hashMessage.js";
1319
import { hashTypedData } from "../../../utils/hashing/hashTypedData.js";
1420
import type { SignableMessage } from "../../../utils/types.js";
21+
import type { Account } from "../../../wallets/interfaces/wallet.js";
1522
import type { SmartAccountOptions } from "../types.js";
1623
import { prepareCreateAccount } from "./calls.js";
1724

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({
1932
accountContract,
2033
factoryContract,
2134
options,
@@ -55,40 +68,51 @@ export async function deployAndSignMessage({
5568
sig = await options.personalAccount.signMessage({ message });
5669
}
5770

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");
66-
}
67-
const initCode = await encode(deployTx);
68-
const erc6492Sig = serializeErc6492Signature({
69-
address: factoryContract.address,
70-
data: initCode,
71-
signature: sig,
72-
});
71+
const isDeployed = await isContractDeployed(accountContract);
72+
if (isDeployed) {
73+
const isValid = await verifyEip1271Signature({
74+
hash: originalMsgHash,
75+
signature: sig,
76+
contract: accountContract,
77+
});
78+
if (isValid) {
79+
return sig;
80+
}
81+
throw new Error("Failed to verify signature");
82+
} else {
83+
const deployTx = prepareCreateAccount({
84+
factoryContract,
85+
adminAddress: options.personalAccount.address,
86+
accountSalt: options.overrides?.accountSalt,
87+
createAccountOverride: options.overrides?.createAccount,
88+
});
89+
if (!deployTx) {
90+
throw new Error("Create account override not provided");
91+
}
92+
const initCode = await encode(deployTx);
93+
const erc6492Sig = serializeErc6492Signature({
94+
address: factoryContract.address,
95+
data: initCode,
96+
signature: sig,
97+
});
7398

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-
});
99+
// check if the signature is valid
100+
const isValid = await verifyHash({
101+
hash: originalMsgHash,
102+
signature: erc6492Sig,
103+
address: accountContract.address,
104+
chain: accountContract.chain,
105+
client: accountContract.client,
106+
});
82107

83-
if (isValid) {
84-
return erc6492Sig;
108+
if (isValid) {
109+
return erc6492Sig;
110+
}
111+
throw new Error("Unable to verify ERC-6492 signature after signing.");
85112
}
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-
);
89113
}
90114

91-
export async function deployAndSignTypedData<
115+
export async function smartAccountSignTypedData<
92116
const typedData extends ox__TypedData.TypedData | Record<string, unknown>,
93117
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
94118
>({
@@ -142,37 +166,50 @@ export async function deployAndSignTypedData<
142166
sig = await options.personalAccount.signTypedData(typedData);
143167
}
144168

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");
153-
}
154-
const initCode = await encode(deployTx);
155-
const erc6492Sig = serializeErc6492Signature({
156-
address: factoryContract.address,
157-
data: initCode,
158-
signature: sig,
159-
});
169+
const isDeployed = await isContractDeployed(accountContract);
170+
if (isDeployed) {
171+
const isValid = await verifyEip1271Signature({
172+
hash: originalMsgHash,
173+
signature: sig,
174+
contract: accountContract,
175+
});
176+
if (isValid) {
177+
return sig;
178+
}
179+
throw new Error("Failed to verify signature");
180+
} else {
181+
const deployTx = prepareCreateAccount({
182+
factoryContract,
183+
adminAddress: options.personalAccount.address,
184+
accountSalt: options.overrides?.accountSalt,
185+
createAccountOverride: options.overrides?.createAccount,
186+
});
187+
if (!deployTx) {
188+
throw new Error("Create account override not provided");
189+
}
190+
const initCode = await encode(deployTx);
191+
const erc6492Sig = serializeErc6492Signature({
192+
address: factoryContract.address,
193+
data: initCode,
194+
signature: sig,
195+
});
160196

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+
// check if the signature is valid
198+
const isValid = await verifyHash({
199+
hash: originalMsgHash,
200+
signature: erc6492Sig,
201+
address: accountContract.address,
202+
chain: accountContract.chain,
203+
client: accountContract.client,
204+
});
169205

170-
if (isValid) {
171-
return erc6492Sig;
206+
if (isValid) {
207+
return erc6492Sig;
208+
}
209+
throw new Error(
210+
"Unable to verify signature on smart account, please make sure the admin wallet has permissions and the signature is valid.",
211+
);
172212
}
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-
);
176213
}
177214

178215
export async function confirmContractDeployment(args: {
@@ -229,3 +266,61 @@ async function checkFor712Factory({
229266
return false;
230267
}
231268
}
269+
270+
/**
271+
* Deployes a smart account via a dummy transaction.
272+
*
273+
* @param args - Arguments for the deployment.
274+
* @param args.smartAccount - The smart account to deploy.
275+
* @param args.chain - The chain to deploy on.
276+
* @param args.client - The client to use for the deployment.
277+
* @param args.accountContract - The account contract to deploy.
278+
*
279+
* @example
280+
* ```ts
281+
* import { deploySmartAccount } from "thirdweb";
282+
*
283+
* const account = await deploySmartAccount({
284+
* smartAccount,
285+
* chain,
286+
* client,
287+
* accountContract,
288+
* });
289+
* ```
290+
*
291+
* @wallets
292+
*/
293+
export async function deploySmartAccount(args: {
294+
smartAccount: Account;
295+
chain: Chain;
296+
client: ThirdwebClient;
297+
accountContract: ThirdwebContract;
298+
}) {
299+
const { chain, client, smartAccount, accountContract } = args;
300+
const isDeployed = await isContractDeployed(accountContract);
301+
if (isDeployed) {
302+
return;
303+
}
304+
305+
const [{ sendTransaction }, { prepareTransaction }] = await Promise.all([
306+
import("../../../transaction/actions/send-transaction.js"),
307+
import("../../../transaction/prepare-transaction.js"),
308+
]);
309+
const dummyTx = prepareTransaction({
310+
client: client,
311+
chain: chain,
312+
to: accountContract.address,
313+
value: 0n,
314+
gas: 50000n, // force gas to avoid simulation error
315+
});
316+
const deployResult = await sendTransaction({
317+
transaction: dummyTx,
318+
account: smartAccount,
319+
});
320+
321+
await confirmContractDeployment({
322+
accountContract,
323+
});
324+
325+
return deployResult;
326+
}

0 commit comments

Comments
 (0)