Skip to content

Commit 7049694

Browse files
[SDK] Add enclave wallet message signing and minimal account implementation
1 parent e9069c9 commit 7049694

File tree

321 files changed

+2844
-8
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

321 files changed

+2844
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import ThirdwebProvider from "@/components/thirdweb-provider";
2+
import { metadataBase } from "@/lib/constants";
3+
import type { Metadata } from "next";
4+
import { Eip7702SmartAccountPreview } from "../../../../components/account-abstraction/7702-smart-account";
5+
import { PageLayout } from "../../../../components/blocks/APIHeader";
6+
import { CodeExample } from "../../../../components/code/code-example";
7+
8+
export const metadata: Metadata = {
9+
metadataBase,
10+
title: "EIP-7702 Smart Accounts",
11+
description:
12+
"EIP-7702 smart accounts allow you to turn your EOA into a smart account with no code changes",
13+
};
14+
15+
export default function Page() {
16+
return (
17+
<ThirdwebProvider>
18+
<PageLayout
19+
title="EIP-7702 Smart Accounts"
20+
description={
21+
<>
22+
EIP-7702 smart accounts allow you to turn your EOA into a smart
23+
account with no code changes.
24+
</>
25+
}
26+
docsLink="https://portal.thirdweb.com/connect/account-abstraction/overview?utm_source=playground"
27+
>
28+
<Eip7702SmartAccount />
29+
</PageLayout>
30+
</ThirdwebProvider>
31+
);
32+
}
33+
34+
function Eip7702SmartAccount() {
35+
return (
36+
<>
37+
<CodeExample
38+
preview={<Eip7702SmartAccountPreview />}
39+
code={`\
40+
import { claimTo } from "thirdweb/extensions/erc1155";
41+
import { TransactionButton } from "thirdweb/react";
42+
43+
function App() {
44+
return (
45+
<>
46+
<ConnectButton
47+
client={client}
48+
accountAbstraction={{
49+
chain: zkSyncSepolia, // zkSync chain with native AA
50+
sponsorGas: true, // sponsor gas for all transactions
51+
}}
52+
connectButton={{
53+
label: "Login to mint this Kitten!",
54+
}}
55+
/>
56+
{/* since sponsorGas is true, transactions will be sponsored */}
57+
<TransactionButton
58+
transaction={() =>
59+
claimTo({
60+
contract,
61+
to: "0x123...",
62+
tokenId: 0n,
63+
quantity: 1n,
64+
})
65+
}
66+
>
67+
Mint
68+
</TransactionButton>
69+
</>
70+
);
71+
}`}
72+
lang="tsx"
73+
/>
74+
</>
75+
);
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { getContract, sendTransaction } from "thirdweb";
5+
import { sepolia } from "thirdweb/chains";
6+
import { claimTo } from "thirdweb/extensions/erc1155";
7+
import { getNFT, getOwnedNFTs } from "thirdweb/extensions/erc1155";
8+
import {
9+
ConnectButton,
10+
MediaRenderer,
11+
TransactionButton,
12+
useActiveAccount,
13+
useReadContract,
14+
} from "thirdweb/react";
15+
import { shortenHex } from "thirdweb/utils";
16+
import { inAppWallet } from "thirdweb/wallets";
17+
import { engineAccount } from "thirdweb/wallets/engine";
18+
import { create7702MinimalAccount } from "thirdweb/wallets/smart";
19+
import { THIRDWEB_CLIENT } from "../../lib/client";
20+
import { Button } from "../ui/button";
21+
22+
const chain = sepolia;
23+
const editionDropAddress = "0x7B3e0B8353Ad5cD6C60355B50550F63335752f9F";
24+
const editionDropTokenId = 1n;
25+
26+
const editionDropContract = getContract({
27+
address: editionDropAddress,
28+
chain,
29+
client: THIRDWEB_CLIENT,
30+
});
31+
32+
export function Eip7702SmartAccountPreview() {
33+
const [txHash, setTxHash] = useState<string | null>(null);
34+
const activeEOA = useActiveAccount();
35+
const { data: nft, isLoading: isNftLoading } = useReadContract(getNFT, {
36+
contract: editionDropContract,
37+
tokenId: editionDropTokenId,
38+
});
39+
const { data: ownedNfts } = useReadContract(getOwnedNFTs, {
40+
contract: editionDropContract,
41+
// biome-ignore lint/style/noNonNullAssertion: handled by queryOptions
42+
address: activeEOA?.address!,
43+
queryOptions: { enabled: !!activeEOA },
44+
});
45+
46+
return (
47+
<div className="flex flex-col items-center justify-center gap-4">
48+
{isNftLoading ? (
49+
<div className="mt-24 w-full">Loading...</div>
50+
) : (
51+
<>
52+
<div className="flex flex-col justify-center gap-2 p-2">
53+
<ConnectButton
54+
client={THIRDWEB_CLIENT}
55+
chain={chain}
56+
wallets={[inAppWallet()]}
57+
connectButton={{
58+
label: "Login to mint!",
59+
}}
60+
/>
61+
</div>
62+
{nft ? (
63+
<MediaRenderer
64+
client={THIRDWEB_CLIENT}
65+
src={nft.metadata.image}
66+
style={{ width: "300px", marginTop: "10px" }}
67+
/>
68+
) : null}
69+
{activeEOA ? (
70+
<div className="flex flex-col justify-center gap-4 p-2">
71+
<p className="mb-2 text-center font-semibold">
72+
You own {ownedNfts?.[0]?.quantityOwned.toString() || "0"}{" "}
73+
{nft?.metadata?.name}
74+
</p>
75+
<Button
76+
onClick={async () => {
77+
const executor = engineAccount({
78+
engineUrl:
79+
"https://tw-unreal-demo.engine-usw2.thirdweb.com",
80+
authToken:
81+
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIweEEyMjRjRTVFMjM2ZDM5RkJkYzM3MzJGYzdGNzk4NWM5NDVhRTkxNmIiLCJzdWIiOiIweDRmQTkyMzBmNEU4OTc4NDYyY0U3QmY4ZTZiNWEyNTg4ZGE1RjQyNjQiLCJhdWQiOiJ0aGlyZHdlYi5jb20iLCJleHAiOjQ4OTk0MDg0NTUsIm5iZiI6MTc0NTgwODQ1NSwiaWF0IjoxNzQ1ODA4NDU1LCJqdGkiOiI2NTRlYjM0MC0yMTYxLTQ1ZTYtYWY2Mi0zYTA5N2QzOWJlZDUiLCJjdHgiOnsicGVybWlzc2lvbnMiOiJBRE1JTiJ9fQ.MHhkOTc5YWQzYWU0Yjg5NTRmZWZjZjMyOGYwYjRlNWJkOTdkZDAxZGUxMzkxOGFiNDcxNmViODUwNDAxMjIxMzNmNDQzODExMGZiMTQ0NGIwMjMyNjU5MmZiMTAzOTBjOGY5NWM3M2NlMWM4M2M4ODNmYmI2YzQ1ODg4MDBkZjNjZDFj",
82+
walletAddress: "0x059be73eFc2A234BE5993802d13333266878f8E8",
83+
});
84+
const smartAccount = create7702MinimalAccount({
85+
client: THIRDWEB_CLIENT,
86+
adminAccount: activeEOA,
87+
// executorAccount: executor,
88+
});
89+
const result = await sendTransaction({
90+
transaction: claimTo({
91+
contract: editionDropContract,
92+
tokenId: editionDropTokenId,
93+
to: activeEOA.address,
94+
quantity: 1n,
95+
}),
96+
account: smartAccount,
97+
});
98+
setTxHash(result.transactionHash);
99+
}}
100+
>
101+
Mint Smart
102+
</Button>
103+
<TransactionButton
104+
transaction={() =>
105+
claimTo({
106+
contract: editionDropContract,
107+
tokenId: editionDropTokenId,
108+
to: activeEOA.address,
109+
quantity: 1n,
110+
})
111+
}
112+
payModal={{
113+
metadata: nft?.metadata,
114+
}}
115+
onError={(error) => {
116+
alert(`Error: ${error.message}`);
117+
}}
118+
onClick={() => {
119+
setTxHash(null);
120+
}}
121+
onTransactionConfirmed={async (receipt) => {
122+
setTxHash(receipt.transactionHash);
123+
}}
124+
>
125+
Mint EOA
126+
</TransactionButton>
127+
</div>
128+
) : null}
129+
{txHash ? (
130+
<div className="flex flex-col justify-center p-2">
131+
<p className="mb-2 text-center text-green-500">
132+
Minted! Tx Hash:{" "}
133+
<a
134+
href={`${chain.blockExplorers?.[0]?.url}/tx/${txHash}`}
135+
target="_blank"
136+
rel="noopener noreferrer"
137+
className="underline"
138+
>
139+
{shortenHex(txHash)}
140+
</a>
141+
</p>
142+
</div>
143+
) : null}
144+
</>
145+
)}
146+
</div>
147+
);
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
"error AllowanceExceeded(uint256 allowanceUsage, uint256 limit, uint64 period)",
3+
"error CallPolicyViolated(address target, bytes4 selector)",
4+
"error CallReverted()",
5+
"error ConditionFailed(bytes32 param, bytes32 refValue, uint8 condition)",
6+
"error InvalidDataLength(uint256 actualLength, uint256 expectedLength)",
7+
"error InvalidSignature(address msgSender, address thisAddress)",
8+
"error LifetimeUsageExceeded(uint256 lifetimeUsage, uint256 limit)",
9+
"error MaxValueExceeded(uint256 value, uint256 maxValuePerUse)",
10+
"error NoCallsToExecute()",
11+
"error SessionExpired()",
12+
"error SessionExpiresTooSoon()",
13+
"error SessionZeroSigner()",
14+
"error TransferPolicyViolated(address target)",
15+
"error UIDAlreadyProcessed()",
16+
"event Executed(address indexed to, uint256 value, bytes data)",
17+
"event SessionCreated(address indexed signer, (address signer, bool isWildcard, uint256 expiresAt, (address target, bytes4 selector, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit, (uint8 condition, uint64 index, bytes32 refValue, (uint8 limitType, uint256 limit, uint256 period) limit)[] constraints)[] callPolicies, (address target, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit)[] transferPolicies, bytes32 uid) sessionSpec)",
18+
"function createSessionWithSig((address signer, bool isWildcard, uint256 expiresAt, (address target, bytes4 selector, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit, (uint8 condition, uint64 index, bytes32 refValue, (uint8 limitType, uint256 limit, uint256 period) limit)[] constraints)[] callPolicies, (address target, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit)[] transferPolicies, bytes32 uid) sessionSpec, bytes signature)",
19+
"function eip712Domain() view returns (bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)",
20+
"function execute((address target, uint256 value, bytes data)[] calls) payable",
21+
"function executeWithSig(((address target, uint256 value, bytes data)[] calls, bytes32 uid) wrappedCalls, bytes signature) payable",
22+
"function getCallPoliciesForSigner(address signer) view returns ((address target, bytes4 selector, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit, (uint8 condition, uint64 index, bytes32 refValue, (uint8 limitType, uint256 limit, uint256 period) limit)[] constraints)[])",
23+
"function getSessionExpirationForSigner(address signer) view returns (uint256)",
24+
"function getSessionStateForSigner(address signer) view returns (((uint256 remaining, address target, bytes4 selector, uint256 index)[] transferValue, (uint256 remaining, address target, bytes4 selector, uint256 index)[] callValue, (uint256 remaining, address target, bytes4 selector, uint256 index)[] callParams))",
25+
"function getTransferPoliciesForSigner(address signer) view returns ((address target, uint256 maxValuePerUse, (uint8 limitType, uint256 limit, uint256 period) valueLimit)[])",
26+
"function isWildcardSigner(address signer) view returns (bool)"
27+
]

packages/thirdweb/scripts/generate/generate.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ export function ${f.name}(
336336
nonce: async () => (await asyncOptions()).overrides?.nonce,
337337
extraGas: async () => (await asyncOptions()).overrides?.extraGas,
338338
erc20Value: async () => (await asyncOptions()).overrides?.erc20Value,
339+
authorizationList: async () => (await asyncOptions()).overrides?.authorizationList,
339340
`
340341
: ""
341342
}
@@ -885,7 +886,7 @@ async function main() {
885886
for (const folder of folders) {
886887
const OUT_PATH = join(EXTENSIONS_FOLDER, folder, "__generated__");
887888
// delete the "__generated__" folder inside the extension folder
888-
await rmdir(OUT_PATH, { recursive: true });
889+
await rmdir(OUT_PATH, { recursive: true }).catch(() => {});
889890

890891
// create the "__generated__" folder inside the extension folder
891892
await mkdir(OUT_PATH, { recursive: true });

packages/thirdweb/src/contract/actions/get-bytecode.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export function getBytecode(contract: ThirdwebContract): Promise<Hex> {
2727
address: contract.address,
2828
blockTag: "latest",
2929
});
30+
console.log("getBytecode", result);
3031
if (result === "0x") {
3132
BYTECODE_CACHE.delete(contract);
3233
}

packages/thirdweb/src/contract/actions/resolve-abi.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { type Abi, formatAbi, parseAbi } from "abitype";
2+
import { sepolia } from "viem/chains";
23
import { download } from "../../storage/download.js";
34
import { getClientFetch } from "../../utils/fetch.js";
45
import { withCache } from "../../utils/promise/withCache.js";
@@ -39,7 +40,11 @@ export function resolveContractAbi<abi extends Abi>(
3940
}
4041

4142
// for local chains, we need to resolve the composite abi from bytecode
42-
if (contract.chain.id === 31337 || contract.chain.id === 1337) {
43+
if (
44+
contract.chain.id === 31337 ||
45+
contract.chain.id === 1337 ||
46+
contract.chain.id === sepolia.id // FIXME remove this once contract API handles 7702 delegation
47+
) {
4348
return (await resolveCompositeAbi(contract as ThirdwebContract)) as abi;
4449
}
4550

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

+2
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,5 @@ export {
4242
DEFAULT_ACCOUNT_FACTORY_V0_7,
4343
TokenPaymaster,
4444
} from "../../wallets/smart/lib/constants.js";
45+
46+
export { create7702MinimalAccount } from "../../wallets/in-app/core/eip7702/minimal-account.js";

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC1155.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC1155WithSignature.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC20.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC20WithSignature.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC721.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropERC721WithSignature.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/airdropNativeToken.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/claimERC1155.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/claimERC20.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/claimERC721.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/thirdweb/src/extensions/airdrop/__generated__/Airdrop/write/initialize.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)