export createAndSignUserOp (#4454)

## Problem solved

Short description of the bug fixed or feature added

<!-- start pr-codex -->

---

## PR-Codex overview
This PR exposes a new utility function `createAndSignUserOp` in `thirdweb` package.

### Detailed summary
- Added `createAndSignUserOp` utility function
- Imported `toSerializableTransaction`
- Updated function `getAccountNonce`
- Added `createAndSignUserOp` function with detailed documentation

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
joaquim-verges
2024-09-07 09:17:29 +00:00
parent 88f89f2d84
commit c546b65e17
3 changed files with 102 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"thirdweb": minor
---
Expose `createAndSignUserOp` utility function
@@ -4,6 +4,7 @@ export {
waitForUserOpReceipt,
createUnsignedUserOp,
signUserOp,
createAndSignUserOp,
} from "../../wallets/smart/lib/userop.js";
export {
@@ -10,6 +10,7 @@ import { getUserOpHash as getUserOpHashV06 } from "../../../extensions/erc4337/_
import { getUserOpHash as getUserOpHashV07 } from "../../../extensions/erc4337/__generated__/IEntryPoint_v07/read/getUserOpHash.js";
import { getDefaultGasOverrides } from "../../../gas/fee-data.js";
import { encode } from "../../../transaction/actions/encode.js";
import { toSerializableTransaction } from "../../../transaction/actions/to-serializable-transaction.js";
import type { PreparedTransaction } from "../../../transaction/prepare-transaction.js";
import type { TransactionReceipt } from "../../../transaction/types.js";
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
@@ -30,11 +31,17 @@ import {
getUserOpGasFees,
getUserOpReceipt,
} from "./bundler.js";
import { prepareCreateAccount } from "./calls.js";
import {
predictAddress,
prepareBatchExecute,
prepareCreateAccount,
prepareExecute,
} from "./calls.js";
import {
DUMMY_SIGNATURE,
ENTRYPOINT_ADDRESS_v0_6,
ENTRYPOINT_ADDRESS_v0_7,
getDefaultAccountFactory,
getDefaultBundlerUrl,
getEntryPointVersion,
} from "./constants.js";
@@ -593,3 +600,91 @@ async function getAccountNonce(options: {
sender: accountContract.address,
});
}
/**
* Create and sign a user operation.
* @param options - The options for creating and signing the user operation
* @returns - The signed user operation
* @example
* ```ts
* import { createAndSignUserOp } from "thirdweb/wallets/smart";
*
* const userOp = await createAndSignUserOp({
* client,
* adminAccount,
* smartWalletOptions,
* transactions,
* });
* ```
* @walletUtils
*/
export async function createAndSignUserOp(options: {
transactions: PreparedTransaction[];
adminAccount: Account;
client: ThirdwebClient;
smartWalletOptions: SmartWalletOptions;
}) {
const config = options.smartWalletOptions;
const factoryContract = getContract({
address:
config.factoryAddress ||
getDefaultAccountFactory(config.overrides?.entrypointAddress),
chain: config.chain,
client: options.client,
});
const accountAddress = await predictAddress({
factoryContract,
adminAddress: options.adminAccount.address,
predictAddressOverride: config.overrides?.predictAddress,
accountSalt: config.overrides?.accountSalt,
accountAddress: config.overrides?.accountAddress,
});
const accountContract = getContract({
address: accountAddress,
chain: config.chain,
client: options.client,
});
let executeTx: PreparedTransaction;
if (options.transactions.length === 1) {
const tx = options.transactions[0] as PreparedTransaction;
const serializedTx = await toSerializableTransaction({
transaction: tx,
});
executeTx = prepareExecute({
accountContract,
transaction: serializedTx,
executeOverride: config.overrides?.execute,
});
} else {
const serializedTxs = await Promise.all(
options.transactions.map((tx) =>
toSerializableTransaction({
transaction: tx,
}),
),
);
executeTx = prepareBatchExecute({
accountContract,
transactions: serializedTxs,
executeBatchOverride: config.overrides?.executeBatch,
});
}
const unsignedUserOp = await createUnsignedUserOp({
transaction: executeTx,
factoryContract,
accountContract,
adminAddress: options.adminAccount.address,
sponsorGas: "sponsorGas" in config ? config.sponsorGas : config.gasless,
overrides: config.overrides,
});
const signedUserOp = await signUserOp({
client: options.client,
chain: config.chain,
adminAccount: options.adminAccount,
entrypointAddress: config.overrides?.entrypointAddress,
userOp: unsignedUserOp,
});
return signedUserOp;
}