Skip to content

Commit 0394eb2

Browse files
feat: add ERC4337 account / factory extensions (#2606)
Co-authored-by: Jonas Daniels <jonas.daniels@outlook.com>
1 parent 7234e6b commit 0394eb2

File tree

18 files changed

+613
-115
lines changed

18 files changed

+613
-115
lines changed

.changeset/soft-pumpkins-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Add ERC4337 extensions

packages/thirdweb/src/exports/deploys.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ export {
1818
type ERC1155ContractType,
1919
deployERC1155Contract,
2020
} from "../extensions/prebuilts/deploy-erc1155.js";
21+
22+
export { prepareDirectDeployTransaction } from "../contract/deployment/deploy-with-abi.js";
23+
export { deployViaAutoFactory } from "../contract/deployment/deploy-via-autofactory.js";
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// ACCOUNT
2+
export {
3+
type AddAdminOptions,
4+
addAdmin,
5+
} from "../../extensions/erc4337/account/addAdmin.js";
6+
7+
export {
8+
type RemoveAdminOptions,
9+
removeAdmin,
10+
} from "../../extensions/erc4337/account/removeAdmin.js";
11+
12+
export {
13+
type AddSessionKeyOptions,
14+
addSessionKey,
15+
} from "../../extensions/erc4337/account/addSessionKey.js";
16+
17+
export {
18+
type RemoveSessionKeyOptions,
19+
removeSessionKey,
20+
} from "../../extensions/erc4337/account/removeSessionKey.js";
21+
22+
export { getAllActiveSigners } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/getAllActiveSigners.js";
23+
export { getAllAdmins } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/getAllAdmins.js";
24+
export { getAllSigners } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/getAllSigners.js";
25+
export {
26+
getPermissionsForSigner,
27+
type GetPermissionsForSignerParams,
28+
} from "../../extensions/erc4337/__generated__/IAccountPermissions/read/getPermissionsForSigner.js";
29+
export {
30+
isActiveSigner,
31+
type IsActiveSignerParams,
32+
} from "../../extensions/erc4337/__generated__/IAccountPermissions/read/isActiveSigner.js";
33+
export { isAdmin } from "../../extensions/erc4337/__generated__/IAccountPermissions/read/isAdmin.js";
34+
export { adminUpdatedEvent } from "../../extensions/erc4337/__generated__/IAccountPermissions/events/AdminUpdated.js";
35+
export { signerPermissionsUpdatedEvent } from "../../extensions/erc4337/__generated__/IAccountPermissions/events/SignerPermissionsUpdated.js";
36+
37+
// FACTORY
38+
export { getAllAccounts } from "../../extensions/erc4337/__generated__/IAccountFactory/read/getAllAccounts.js";
39+
export {
40+
getAccountsOfSigner,
41+
type GetAccountsOfSignerParams,
42+
} from "../../extensions/erc4337/__generated__/IAccountFactory/read/getAccountsOfSigner.js";
43+
export {
44+
getAddress as predictAccountAddress,
45+
type GetAddressParams as PredictAccountAddressParams,
46+
} from "../../extensions/erc4337/__generated__/IAccountFactory/read/getAddress.js";
47+
48+
// ENTRYPOINT
49+
50+
export { accountDeployedEvent } from "../../extensions/erc4337/__generated__/IEntryPoint/events/AccountDeployed.js";
51+
export { userOperationEventEvent } from "../../extensions/erc4337/__generated__/IEntryPoint/events/UserOperationEvent.js";
52+
export { userOperationRevertReasonEvent } from "../../extensions/erc4337/__generated__/IEntryPoint/events/UserOperationRevertReason.js";
53+
export {
54+
getUserOpHash,
55+
type GetUserOpHashParams,
56+
} from "../../extensions/erc4337/__generated__/IEntryPoint/read/getUserOpHash.js";
57+
export {
58+
simulateHandleOp,
59+
type SimulateHandleOpParams,
60+
} from "../../extensions/erc4337/__generated__/IEntryPoint/write/simulateHandleOp.js";
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { BaseTransactionOptions } from "../../../transaction/types.js";
2+
import type { Account } from "../../../wallets/interfaces/wallet.js";
3+
import { setPermissionsForSigner } from "../__generated__/IAccountPermissions/write/setPermissionsForSigner.js";
4+
import { defaultPermissionsForAdmin, signPermissionRequest } from "./common.js";
5+
6+
export type AddAdminOptions = {
7+
/**
8+
* The admin account that will perform the operation.
9+
*/
10+
account: Account;
11+
/**
12+
* The address to add as an admin.
13+
*/
14+
adminAddress: string;
15+
};
16+
17+
/**
18+
* Adds admin permissions for a specified address.
19+
* @param options - The options for the addAdmin function.
20+
* @returns The transaction object to be sent.
21+
* @example
22+
* ```
23+
* import { addAdmin } from 'thirdweb/extensions/erc4337';
24+
*
25+
* const transaction = addAdmin({
26+
* contract,
27+
* account,
28+
* adminAddress: '0x...'
29+
* });
30+
* await sendTransaction({ transaction, account });
31+
* ```
32+
* @extension ERC4337
33+
*/
34+
export function addAdmin(options: BaseTransactionOptions<AddAdminOptions>) {
35+
const { contract, account, adminAddress } = options;
36+
return setPermissionsForSigner({
37+
contract,
38+
async asyncParams() {
39+
const { req, signature } = await signPermissionRequest({
40+
account,
41+
contract,
42+
req: await defaultPermissionsForAdmin({
43+
target: adminAddress,
44+
action: "add-admin",
45+
}),
46+
});
47+
return { signature, req };
48+
},
49+
});
50+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { BaseTransactionOptions } from "../../../transaction/types.js";
2+
import type { Account } from "../../../wallets/interfaces/wallet.js";
3+
import { setPermissionsForSigner } from "../__generated__/IAccountPermissions/write/setPermissionsForSigner.js";
4+
import { signPermissionRequest, toContractPermissions } from "./common.js";
5+
import type { AccountPermissions } from "./types.js";
6+
7+
export type AddSessionKeyOptions = {
8+
/**
9+
* The adming account that will perform the operation.
10+
*/
11+
account: Account;
12+
/**
13+
* The address to add as a session key.
14+
*/
15+
sessionKeyAddress: string;
16+
/**
17+
* The permissions to assign to the session key.
18+
*/
19+
permissions: AccountPermissions;
20+
};
21+
22+
/**
23+
* Adds session key permissions for a specified address.
24+
* @param options - The options for the removeSessionKey function.
25+
* @returns The transaction object to be sent.
26+
* @example
27+
* ```
28+
* import { addSessionKey } from 'thirdweb/extensions/erc4337';
29+
*
30+
* const transaction = addSessionKey({
31+
* contract,
32+
* account,
33+
* sessionKeyAddress,
34+
* permissions: {
35+
* approvedTargets: ['0x...'],
36+
* nativeTokenLimitPerTransaction: 0.1, // in ETH
37+
* permissionStartTimestamp: new Date(),
38+
* permissionEndTimestamp: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), // 1 year from now
39+
* }
40+
* });
41+
* await sendTransaction({ transaction, account });
42+
* ```
43+
* @extension ERC4337
44+
*/
45+
export function addSessionKey(
46+
options: BaseTransactionOptions<AddSessionKeyOptions>,
47+
) {
48+
const { contract, sessionKeyAddress, account, permissions } = options;
49+
return setPermissionsForSigner({
50+
contract,
51+
async asyncParams() {
52+
const { req, signature } = await signPermissionRequest({
53+
account,
54+
contract,
55+
req: await toContractPermissions({
56+
target: sessionKeyAddress,
57+
permissions,
58+
}),
59+
});
60+
return { signature, req };
61+
},
62+
});
63+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { ADDRESS_ZERO } from "../../../constants/addresses.js";
2+
import type { ThirdwebContract } from "../../../contract/contract.js";
3+
import { dateToSeconds, tenYearsFromNow } from "../../../utils/date.js";
4+
import { toWei } from "../../../utils/units.js";
5+
import { randomBytes32 } from "../../../utils/uuid.js";
6+
import type { Account } from "../../../wallets/interfaces/wallet.js";
7+
import type { SetPermissionsForSignerParams } from "../__generated__/IAccountPermissions/write/setPermissionsForSigner.js";
8+
import { SignerPermissionRequest, type AccountPermissions } from "./types.js";
9+
10+
/**
11+
* @internal
12+
*/
13+
export async function signPermissionRequest(options: {
14+
account: Account;
15+
contract: ThirdwebContract;
16+
req: SetPermissionsForSignerParams["req"];
17+
}) {
18+
const { account, contract, req } = options;
19+
const signature = await account.signTypedData({
20+
domain: {
21+
name: "Account",
22+
version: "1",
23+
verifyingContract: contract.address,
24+
chainId: contract.chain.id,
25+
},
26+
primaryType: "SignerPermissionRequest",
27+
types: { SignerPermissionRequest },
28+
message: req,
29+
});
30+
return { req, signature };
31+
}
32+
33+
/**
34+
* @internal
35+
*/
36+
export async function toContractPermissions(options: {
37+
target: string;
38+
permissions: AccountPermissions;
39+
}): Promise<SetPermissionsForSignerParams["req"]> {
40+
const { target, permissions } = options;
41+
return {
42+
approvedTargets:
43+
permissions.approvedTargets === "*"
44+
? [ADDRESS_ZERO]
45+
: permissions.approvedTargets,
46+
nativeTokenLimitPerTransaction: toWei(
47+
permissions.nativeTokenLimitPerTransaction?.toString() || "0",
48+
),
49+
permissionStartTimestamp: dateToSeconds(
50+
permissions.permissionStartTimestamp || new Date(0),
51+
),
52+
permissionEndTimestamp: dateToSeconds(
53+
permissions.permissionEndTimestamp || tenYearsFromNow(),
54+
),
55+
reqValidityStartTimestamp: 0n,
56+
reqValidityEndTimestamp: dateToSeconds(tenYearsFromNow()),
57+
uid: await randomBytes32(),
58+
isAdmin: 0, // session key flag
59+
signer: target,
60+
};
61+
}
62+
63+
/**
64+
* @internal
65+
*/
66+
export async function defaultPermissionsForAdmin(options: {
67+
target: string;
68+
action: "add-admin" | "remove-admin";
69+
}): Promise<SetPermissionsForSignerParams["req"]> {
70+
const { target, action } = options;
71+
return {
72+
approvedTargets: [],
73+
nativeTokenLimitPerTransaction: 0n,
74+
permissionStartTimestamp: 0n,
75+
permissionEndTimestamp: 0n,
76+
reqValidityStartTimestamp: 0n,
77+
reqValidityEndTimestamp: dateToSeconds(tenYearsFromNow()),
78+
uid: await randomBytes32(),
79+
isAdmin: action === "add-admin" ? 1 : action === "remove-admin" ? 2 : 0,
80+
signer: target,
81+
};
82+
}

0 commit comments

Comments
 (0)