From 7b09d759981d864676f8205c497c85cad419654f Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 15:33:11 +0530 Subject: [PATCH 1/9] Deploy and install stylus modules --- .../modules/common/installPublishedModule.ts | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts b/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts index 6592ce0ce69..f2ce2c074d3 100644 --- a/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts +++ b/packages/thirdweb/src/extensions/modules/common/installPublishedModule.ts @@ -1,6 +1,7 @@ import type { ThirdwebContract } from "../../../contract/contract.js"; import { getOrDeployInfraForPublishedContract } from "../../../contract/deployment/utils/bootstrap.js"; import type { Account } from "../../../wallets/interfaces/wallet.js"; +import { deployPublishedContract } from "../../prebuilts/deploy-published.js"; import { installModule } from "../__generated__/IModularCore/write/installModule.js"; /** @@ -49,17 +50,35 @@ export function installPublishedModule(options: InstallPublishedModuleOptions) { return installModule({ asyncParams: async () => { - const deployedModule = await getOrDeployInfraForPublishedContract({ - account, - chain: contract.chain, - client: contract.client, - constructorParams, - contractId: moduleName, - publisher, - }); + let implementationAddress: string; + + if (moduleName.toLowerCase().includes("stylus")) { + // TODO: switch to deterministic / create2 when available + + implementationAddress = await deployPublishedContract({ + account, + chain: contract.chain, + client: contract.client, + contractParams: constructorParams, + contractId: moduleName, + publisher, + }); + } else { + const deployedModule = await getOrDeployInfraForPublishedContract({ + account, + chain: contract.chain, + client: contract.client, + constructorParams, + contractId: moduleName, + publisher, + }); + + implementationAddress = deployedModule.implementationContract + .address as string; + } return { data: moduleData || "0x", - moduleContract: deployedModule.implementationContract.address as string, + moduleContract: implementationAddress, }; }, contract, From 19378f563ee845b301d5d770432c16d219631867 Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 15:34:32 +0530 Subject: [PATCH 2/9] changeset --- .changeset/sharp-files-cry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sharp-files-cry.md diff --git a/.changeset/sharp-files-cry.md b/.changeset/sharp-files-cry.md new file mode 100644 index 00000000000..0537e5e8d25 --- /dev/null +++ b/.changeset/sharp-files-cry.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +deploy and install stylus modules From 5343a41cfab5675c33dbffe2b34f58cc82f5ac50 Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 20:29:34 +0530 Subject: [PATCH 3/9] compatibility --- .../modules/components/ModuleForm.tsx | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx index 2f55f561935..3a23caf0228 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx +++ b/apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/ModuleForm.tsx @@ -225,6 +225,10 @@ export const InstallModuleForm = (props: InstallModuleFormProps) => { moduleInfo: { bytecodeUri: selectedModule.metadata.bytecodeUri, }, + isStylus: + (selectedModule.metadata.compilers?.stylus && + selectedModule.metadata.compilers?.stylus?.length > 0) || + false, }); }, queryKey: [ @@ -432,6 +436,7 @@ async function isModuleCompatible(options: { moduleInfo: { bytecodeUri: string; }; + isStylus: boolean; client: ThirdwebClient; }) { // 1. get module's bytecode @@ -444,15 +449,17 @@ async function isModuleCompatible(options: { // 2. check compatibility with core and installed modules try { - const isCompatible = await checkModulesCompatibility({ - chain: options.contractInfo.chain, - client: options.client, - coreBytecode: options.contractInfo.bytecode, - moduleBytecodes: [ - moduleBytecode, - ...options.contractInfo.installedModuleBytecodes, - ], - }); + const isCompatible = + options.isStylus || + (await checkModulesCompatibility({ + chain: options.contractInfo.chain, + client: options.client, + coreBytecode: options.contractInfo.bytecode, + moduleBytecodes: [ + moduleBytecode, + ...options.contractInfo.installedModuleBytecodes, + ], + })); return isCompatible; } catch (e) { From 1e16b99172989c0ea88051a44c2023ae9969835d Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 21:09:21 +0530 Subject: [PATCH 4/9] enable stylus modular contracts in explore --- .../src/app/(app)/(dashboard)/explore/data.ts | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts b/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts index ad5d41af915..47fce920220 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts +++ b/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts @@ -183,13 +183,62 @@ const STYLUS = { ], description: "Airdrop your NFTs or tokens to a large number of recipients. Built with Arbitrum Stylus.", - displayName: "Arbitrum Stylus Contracts", + displayName: "Airdrop - Arbitrum Stylus", id: "stylus", name: "Stylus", showInExplore: true, isBeta: true, } satisfies ExploreCategory; +const MODULAR_CONTRACTS_STYLUS = { + contracts: [ + // erc721 token + [ + "thirdweb.eth/ERC721CoreInitializable", + [ + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC721", + "deployer.thirdweb.eth/BatchMetadataERC721", + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC721", + ], + { + description: "ERC721 NFTs that only owners can mint.", + title: "Modular NFT Collection", + }, + ], + // erc1155 token + [ + "thirdweb.eth/ERC1155CoreInitializable", + [ + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC1155", + "deployer.thirdweb.eth/BatchMetadataERC1155", + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC1155", + "deployer.thirdweb.eth/SequentialTokenIdERC1155", + ], + { + description: "ERC1155 NFTs that only owners can mint.", + title: "Modular Edition", + }, + ], + // erc20 token + [ + "thirdweb.eth/ERC20CoreInitializable", + [ + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusMintableERC20", + "0x6453a486d52e0eb6e79ec4491038e2522a926936/StylusTransferableERC20", + ], + { + description: "ERC20 Tokens that only owners can mint.", + title: "Modular Token", + }, + ], + ], + description: + "Collection of highly customizable and upgradeable smart contracts built with the modular contracts framework.", + displayName: "Modular Contracts - Arbitrum Stylus", + id: "modular-contracts", + name: "modular", +} satisfies ExploreCategory; + const CATEGORIES: Record = { [POPULAR.id]: POPULAR, [NFTS.id]: NFTS, @@ -203,6 +252,7 @@ const CATEGORIES: Record = { [COMMERCE.id]: COMMERCE, [STAKING.id]: STAKING, [GOVERNANCE.id]: GOVERNANCE, + [MODULAR_CONTRACTS_STYLUS.id]: MODULAR_CONTRACTS_STYLUS, }; export function getCategory(id: string) { From 4bb0d43725ecf3373d8e4ed5c9586fdfd34a552b Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 22:34:26 +0530 Subject: [PATCH 5/9] show in explore, beta --- apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts b/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts index 47fce920220..6e7adb1fa28 100644 --- a/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts +++ b/apps/dashboard/src/app/(app)/(dashboard)/explore/data.ts @@ -237,6 +237,8 @@ const MODULAR_CONTRACTS_STYLUS = { displayName: "Modular Contracts - Arbitrum Stylus", id: "modular-contracts", name: "modular", + showInExplore: true, + isBeta: true, } satisfies ExploreCategory; const CATEGORIES: Record = { From 02bc9f012aac4333f0ba3f7d13f2ce4b978346c4 Mon Sep 17 00:00:00 2001 From: Yash Date: Sun, 9 Nov 2025 22:41:39 +0530 Subject: [PATCH 6/9] cli templates for creating rust modules --- .../src/cli/commands/stylus/create.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/packages/thirdweb/src/cli/commands/stylus/create.ts b/packages/thirdweb/src/cli/commands/stylus/create.ts index 088a3bcacf3..b89ab822c4a 100644 --- a/packages/thirdweb/src/cli/commands/stylus/create.ts +++ b/packages/thirdweb/src/cli/commands/stylus/create.ts @@ -60,6 +60,12 @@ export async function createStylusProject() { { title: "Airdrop ERC1155", value: "airdrop1155" }, { title: "ZK ERC721", value: "zk-erc721" }, { title: "ZK ERC20", value: "zk-erc20" }, + { title: "Mint module - ERC20", value: "mintable20" }, + { title: "Transfer module - ERC20", value: "transferable20" }, + { title: "Mint module - ERC721", value: "mintable721" }, + { title: "Transfer module - ERC721", value: "transferable721" }, + { title: "Mint module - ERC1155", value: "mintable1155" }, + { title: "Transfer module - ERC1155", value: "transferable1155" }, ], message: "Select a template:", name: "projectType", @@ -133,6 +139,49 @@ export async function createStylusProject() { newProject = spawnSync("git", ["clone", repoUrl, projectName], { stdio: "inherit", }); + } else if (projectType === "mintable20") { + const repoUrl = "git@github.com:thirdweb-example/stylus-mintable-erc20.git"; + spinner.start(`Creating new ERC20 Mintable module: ${projectName}...`); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); + } else if (projectType === "transferable20") { + const repoUrl = + "git@github.com:thirdweb-example/stylus-transferable-erc20.git"; + spinner.start(`Creating new ERC20 Transferable module: ${projectName}...`); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); + } else if (projectType === "mintable721") { + const repoUrl = + "git@github.com:thirdweb-example/stylus-mintable-erc721.git"; + spinner.start(`Creating new ERC721 Mintable module: ${projectName}...`); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); + } else if (projectType === "transferable721") { + const repoUrl = + "git@github.com:thirdweb-example/stylus-transferable-erc721.git"; + spinner.start(`Creating new ERC721 Transferable module: ${projectName}...`); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); + } else if (projectType === "mintable1155") { + const repoUrl = + "git@github.com:thirdweb-example/stylus-mintable-erc1155.git"; + spinner.start(`Creating new ERC1155 Mintable module: ${projectName}...`); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); + } else if (projectType === "transferable1155") { + const repoUrl = + "git@github.com:thirdweb-example/stylus-transferable-erc1155.git"; + spinner.start( + `Creating new ERC1155 Transferable module: ${projectName}...`, + ); + newProject = spawnSync("git", ["clone", repoUrl, projectName], { + stdio: "inherit", + }); } if (!newProject || newProject.status !== 0) { From 883c575ecc10e42a23e9c945d6fd99e3b8bbdf72 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 10 Nov 2025 14:31:11 +0530 Subject: [PATCH 7/9] fix dynamic contract tests --- .../thirdweb/src/contract/deployment/deploy-dynamic.test.ts | 3 +-- .../dynamic-contracts/write/installPublishedExtension.test.ts | 3 +-- .../dynamic-contracts/write/uninstallExtension.test.ts | 3 +-- packages/thirdweb/src/extensions/prebuilts/deploy-published.ts | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts b/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts index a97b780359b..828123dc900 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts @@ -9,8 +9,7 @@ import { getContract } from "../contract.js"; import { deployCloneFactory } from "./utils/bootstrap.js"; describe.runIf(process.env.TW_SECRET_KEY)("deploy dynamic", () => { - // TODO: Fix this test - it.skip.sequential( + it.sequential( "should deploy dynamic contract with extensions", async () => { await deployCloneFactory({ diff --git a/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts b/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts index f9ed58f5266..fa97d3194e5 100644 --- a/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts +++ b/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts @@ -11,8 +11,7 @@ import { sendTransaction } from "../../../transaction/actions/send-transaction.j import { installPublishedExtension } from "./installPublishedExtension.js"; describe.runIf(process.env.TW_SECRET_KEY)("install extension", () => { - // TODO: Fix this test - it.skip.sequential( + it.sequential( "should install extension to a dynamic contract", async () => { await deployCloneFactory({ diff --git a/packages/thirdweb/src/extensions/dynamic-contracts/write/uninstallExtension.test.ts b/packages/thirdweb/src/extensions/dynamic-contracts/write/uninstallExtension.test.ts index 779890b7852..6f21bdd9397 100644 --- a/packages/thirdweb/src/extensions/dynamic-contracts/write/uninstallExtension.test.ts +++ b/packages/thirdweb/src/extensions/dynamic-contracts/write/uninstallExtension.test.ts @@ -11,8 +11,7 @@ import { deployPublishedContract } from "../../prebuilts/deploy-published.js"; import { uninstallExtension } from "./uninstallExtension.js"; describe.runIf(process.env.TW_SECRET_KEY)("uninstall extension", () => { - // TODO: Fix this test - it.skip.sequential( + it.sequential( "should uninstall extension from a dynamic contract", async () => { await deployCloneFactory({ diff --git a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts index 5e4584ad566..715c4dffdb2 100644 --- a/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts +++ b/packages/thirdweb/src/extensions/prebuilts/deploy-published.ts @@ -215,7 +215,7 @@ export async function deployContractfromDeployMetadata( if ( deployMetadata.routerType === "dynamic" && deployMetadata.defaultExtensions && - !isZkSyncChain(chain) + !(await isZkSyncChain(chain)) ) { for (const e of deployMetadata.defaultExtensions) { await getOrDeployInfraForPublishedContract({ From 26c090dbfa8ababc53fb9658582a9b3adb9d1e3b Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 10 Nov 2025 15:41:44 +0530 Subject: [PATCH 8/9] docs --- .../arbitrum-stylus/minting-modules/page.mdx | 160 ++++++++++++ .../arbitrum-stylus/zk-mint/page.mdx | 240 ++++++++++++++++++ apps/portal/src/app/contracts/sidebar.tsx | 8 + 3 files changed, 408 insertions(+) create mode 100644 apps/portal/src/app/contracts/arbitrum-stylus/minting-modules/page.mdx create mode 100644 apps/portal/src/app/contracts/arbitrum-stylus/zk-mint/page.mdx diff --git a/apps/portal/src/app/contracts/arbitrum-stylus/minting-modules/page.mdx b/apps/portal/src/app/contracts/arbitrum-stylus/minting-modules/page.mdx new file mode 100644 index 00000000000..ecc523312cd --- /dev/null +++ b/apps/portal/src/app/contracts/arbitrum-stylus/minting-modules/page.mdx @@ -0,0 +1,160 @@ +import { Step, Steps, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "Stylus Minting Modules | thirdweb Documentation", + description: + "Customizable rust modules written for thirdweb's modular contracts", + image: { + title: "Stylus Minting Modules", + icon: "contracts", + }, +}); + +# Stylus Minting Modules + +You can now install rust based modules into a solidity based [modular contract](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md). + +This plug-n-play model also allows users to write their own custom minting logic in rust, taking advantage of Stylus' interoperability and gas savings. These modules work with existing router contracts and modules written in solidity. + +## Deploy through dashboard + +Deploying a Modular Contract and installing modules is easy through the thirdweb dashboard and ideal when you don't want to modify any code on the contract. + + + + +Navigate to the Modular Contracts section on Explore and select any token contract for your project. + + + +Select Arbitrum Sepolia or any other Stylus-supported network, then select Deploy. Once you have deployed one of these contracts, the modules come pre-installed, both solidity and rust ones. + + + +If you wish to install a custom module, navigate to modules tab on your contract page. Enter the publisher address and select module from the dropdown, and click install. + +You can uninstall a module from the same page. + +Here's a list of prebuilt Rust / Stylus specific modules provided by thirdweb: +- [Mintable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC20) +- [Transferable ERC20](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC20) +- [Mintable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC721) +- [Transferable ERC721](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC721) +- [Mintable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusMintableERC1155) +- [Transferable ERC1155](https://thirdweb.com/0x6453a486d52e0EB6E79Ec4491038E2522a926936/StylusTransferableERC1155) + +You can customize these modules via thirdweb CLI as described below. + + + + + +## Build with CLI + +If you want to modify the contract code or publish a custom module, you can use the thirdweb CLI. + + + + +In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown. + +```bash +npx thirdweb create-stylus +``` + + + +In the `src/lib.rs` file you can modify the contract logic such as adding fees, gating logic, analytics events, and more. + + + + +To build your project, run the following command: + +```bash +cargo stylus build +``` + + + + +You can publish your module and install it to your modular contract. Publishing stores your module metadata in thirdweb’s on-chain registry so anyone (including you) can use that exact version later with a few clicks. + +To publish your module, ensure you have your thirdweb secret key from your created project, then run the following command: + +```bash +npx thirdweb publish-stylus -k YOUR_TW_SECRET_KEY +``` + +Once published, the module is now available to install via thirdweb dashboard from the modules tab of your contract. + + + + +## Interacting with the Contract + +Using the thirdweb SDKs, you can interact with your Stylus Modular contracts / modules to mint tokens, transfer ownership, and more. + +The following describes how to interact with a ERC721 modular contract using thirdweb SDK. (Swap for ERC20 or ERC1155 variants as needed.) + +### Upload metadata + +```ts + import { BatchMetadataERC721 } from "thirdweb/modules"; + + const transaction = BatchMetadataERC721.uploadMetadata({ + contract, + metadatas: [ + { name: "My NFT", description: "This is my NFT" }, + ], + }); + + await sendTransaction({ + transaction, + account, + }); + ``` + +### Mint with role + +```typescript +import { MintableERC721 } from "thirdweb/modules"; + +const transaction = MintableERC721.mintWithRole({ + contract, + to: "0x...", // Address to mint tokens to + nfts: [ + { + name: "My NFT", + description: "This is my NFT", + image: "ipfs://...", + }, + ], +}); + +// Send the transaction +await sendTransaction({ transaction, account }); +``` + +### Set transfer rules + +```ts +import { sendTransaction } from "thirdweb"; +import { TransferableERC20 } from "thirdweb/modules"; + +const transaction = TransferableERC20.setTransferable({ + contract, + enableTransfer: ..., + overrides: { + ... + } +}); + +// Send the transaction +await sendTransaction({ transaction, account }); +``` + +### Resources + +- [Modular Contracts Design Doc](https://github.com/thirdweb-dev/modular-contracts/blob/dev/design-document.md) + diff --git a/apps/portal/src/app/contracts/arbitrum-stylus/zk-mint/page.mdx b/apps/portal/src/app/contracts/arbitrum-stylus/zk-mint/page.mdx new file mode 100644 index 00000000000..f2378156a02 --- /dev/null +++ b/apps/portal/src/app/contracts/arbitrum-stylus/zk-mint/page.mdx @@ -0,0 +1,240 @@ +import { Step, Steps, createMetadata } from "@doc"; + +export const metadata = createMetadata({ + title: "Stylus Zk Mint Contract | thirdweb Documentation", + description: + "Stylus Zk Minting contract and app template", + image: { + title: "Stylus Zk Mint", + icon: "contracts", + }, +}); + +# ZK Minting with Stylus + +You can now create and deploy ZK Proof based token contracts with Stylus. The templates provide you with Stylus contracts, ZK circuit files, and a ready-to-deploy Next.js app + API for proof generation and minting. + +Follow the steps below to learn how to build a privacy-preserving ERC721 token system using Zero-Knowledge proofs on Arbitrum Stylus. Users can mint tokens by proving they own a minimum amount of ETH without revealing their exact balance. However, you can customize the circuits and the contracts to add your own checks for minting. + +## What you'll build + +- ZK Circuit: Proves token ownership without revealing exact balances +- Stylus Contract: Rust-based ERC721 contract that verifies ZK proofs +- Frontend: Next.js app for generating proofs and minting tokens +- Oracle System: Secure balance verification mechanism + +## Prerequisites + +- [Create a project on your thirdweb account](https://thirdweb.com/dashboard) +- Install thirdweb CLI by running `npm install -g thirdweb` +- Install Rust tool chain by running `curl https://sh.rustup.rs -sSf | sh` or visit rust-lang.org +- Install solc by running `npm install -g solc` or visit soliditylang.org +- Install Node version 18 or higher + + + + +In your CLI, run the following command to create a new directory with an template module. Select the module template from the list shown. + +```bash +npx thirdweb create-stylus +``` + +Select **"Stylus ZK ERC721"** from the dropdown menu. This will: +- Clone the repository to your machine +- Set up the project structure +- Install basic dependencies + + + +Install dependencies for all components: + +```bash +# Install root dependencies +pnpm install + +# Install circuit dependencies +cd circuits +pnpm install +cd .. + +# Install frontend dependencies +cd app +pnpm install +cd .. +``` + + + +Run the setup script to generate oracle keys and build the ZK circuit: + +```bash +chmod +x setup.sh +./setup.sh +``` + +This script will: +- Generate a random oracle secret key +- Inject the secret into the ZK circuit +- Compile the circuit with circom +- Generate proving and verification keys +- Create the trusted setup for Groth16 + +**⚠️ Important**: The oracle secret is critical for security. Keep it private! + + + + +### Using thirdweb CLI + + +```bash +cd contracts +npx thirdweb@latest deploy-stylus -k +``` + +### Using Stylus CLI (Alternative) + +```bash +cd contracts +cargo stylus deploy --endpoint arbitrum-sepolia +``` + +Copy the deployed contract address - you'll need it for the frontend. + + + +Update the contract address in your frontend: + +```bash +cd app +# Edit pages/index.tsx or lib/config.ts +# Update ZK_MINT_CONTRACT_ADDRESS with your deployed address +``` + +Create environment file: + +```bash +# app/.env.local +RPC_URL=https://sepolia-rollup.arbitrum.io/rpc +ORACLE_SECRET_KEY= +``` + + + +```bash +cd app +pnpm dev +# Visit http://localhost:3000 +``` + + + +1. **Connect Wallet**: Connect to Arbitrum Sepolia testnet +2. **Generate Proof**: Click "Generate ZK Proof" - this proves you have sufficient balance +3. **Mint Tokens**: Use the proof to mint ERC721 tokens + + + + +## Customizing the ZK Logic + +The above described logic for ZK verification of user balance is provided as a template. You can modify the logic and write your own custom ZK verification code and circuits as described below. + +### Understanding the Circuit + +The core circuit (`circuits/token_ownership.circom`) has these components: + +```txt +template TokenOwnership(oracle_secret) { + // Private inputs (hidden from public) + signal input actual_balance; // Real balance from oracle + signal input salt; // Randomness for uniqueness + + // Public inputs (visible on-chain) + signal input min_required_balance; // Minimum threshold + signal input token_contract_hash; // Which token to check + signal input user_address_hash; // User's address hash + signal input timestamp; // When oracle signed data + signal input oracle_commitment; // Oracle's commitment + + // Output + signal output nullifier; // Prevents replay attacks +} +``` + +### Customization Examples + +#### 1. Change Balance Threshold Logic + +Replace the balance check with custom logic: + +```txt +// Original: Simple greater-than check +component gte = GreaterEqThan(64); +gte.in[0] <== actual_balance; +gte.in[1] <== min_required_balance; +gte.out === 1; + +// Custom: Logarithmic scaling +component log_check = LogarithmicCheck(); +log_check.balance <== actual_balance; +log_check.threshold <== min_required_balance; +log_check.out === 1; +``` + +#### 2. Add Multiple Token Support + +Extend the circuit to verify multiple token balances: + +```txt +template MultiTokenOwnership(oracle_secret, num_tokens) { + signal input actual_balances[num_tokens]; + signal input min_required_balances[num_tokens]; + signal input token_addresses[num_tokens]; + + // Verify each token separately + component checks[num_tokens]; + for (var i = 0; i < num_tokens; i++) { + checks[i] = GreaterEqThan(64); + checks[i].in[0] <== actual_balances[i]; + checks[i].in[1] <== min_required_balances[i]; + checks[i].out === 1; + } +} +``` + +#### 3. Add Time-Based Constraints + +Add expiration logic to proofs: + +```txt +// Add time validation +component time_check = LessThan(64); +time_check.in[0] <== timestamp; +time_check.in[1] <== max_timestamp; // New public input +time_check.out === 1; +``` + +### Rebuilding After Changes + +After modifying the circuit: + +```bash +cd circuits +pnpm run build + +# Re-inject verification keys +cd .. +node scripts/inject-vk.js + +# Recompile contract +cd contracts +cargo build +``` + +### Resources + +- [thirdweb Arbitrum Documentation](https://portal.thirdweb.com/contracts/arbitrum-stylus/stylus-contract) +- [Arbitrum Stylus Documentation](https://docs.arbitrum.io/stylus/concepts/how-it-works) + diff --git a/apps/portal/src/app/contracts/sidebar.tsx b/apps/portal/src/app/contracts/sidebar.tsx index c764a226af2..8512cea9b4b 100644 --- a/apps/portal/src/app/contracts/sidebar.tsx +++ b/apps/portal/src/app/contracts/sidebar.tsx @@ -56,6 +56,14 @@ export const sidebar: SideBar = { href: `${slug}/arbitrum-stylus/airdrop-contract`, name: "Stylus Airdrop Contract", }, + { + href: `${slug}/arbitrum-stylus/zk-mint`, + name: "Stylus Zk Mint", + }, + { + href: `${slug}/arbitrum-stylus/minting-modules`, + name: "Stylus Minting Modules", + }, ], name: "Arbitrum Stylus", }, From 16fc56dd2451001caad40b31ced67fb9b39df492 Mon Sep 17 00:00:00 2001 From: Yash Date: Mon, 10 Nov 2025 15:46:35 +0530 Subject: [PATCH 9/9] lint --- .../deployment/deploy-dynamic.test.ts | 73 ++++++++-------- .../write/installPublishedExtension.test.ts | 83 +++++++++---------- 2 files changed, 75 insertions(+), 81 deletions(-) diff --git a/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts b/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts index 828123dc900..feb8a637e22 100644 --- a/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts +++ b/packages/thirdweb/src/contract/deployment/deploy-dynamic.test.ts @@ -9,47 +9,44 @@ import { getContract } from "../contract.js"; import { deployCloneFactory } from "./utils/bootstrap.js"; describe.runIf(process.env.TW_SECRET_KEY)("deploy dynamic", () => { - it.sequential( - "should deploy dynamic contract with extensions", - async () => { - await deployCloneFactory({ - account: TEST_ACCOUNT_A, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - }); + it.sequential("should deploy dynamic contract with extensions", async () => { + await deployCloneFactory({ + account: TEST_ACCOUNT_A, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); - const deployed = await deployPublishedContract({ - account: TEST_ACCOUNT_A, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - contractId: "EvolvingNFT", - contractParams: { - contractURI: "", - defaultAdmin: TEST_ACCOUNT_A.address, - name: "Evolving nft", - royaltyBps: 0n, - royaltyRecipient: TEST_ACCOUNT_A.address, - saleRecipient: TEST_ACCOUNT_A.address, - symbol: "ENFT", - trustedForwarders: [], - }, - }); + const deployed = await deployPublishedContract({ + account: TEST_ACCOUNT_A, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + contractId: "EvolvingNFT", + contractParams: { + contractURI: "", + defaultAdmin: TEST_ACCOUNT_A.address, + name: "Evolving nft", + royaltyBps: 0n, + royaltyRecipient: TEST_ACCOUNT_A.address, + saleRecipient: TEST_ACCOUNT_A.address, + symbol: "ENFT", + trustedForwarders: [], + }, + }); - expect(deployed).toBeDefined(); + expect(deployed).toBeDefined(); - const contract = getContract({ - address: deployed, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - }); + const contract = getContract({ + address: deployed, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); - const extensions = await readContract({ - contract, - method: resolveMethod("getAllExtensions"), - params: [], - }); + const extensions = await readContract({ + contract, + method: resolveMethod("getAllExtensions"), + params: [], + }); - expect(extensions.length).toEqual(3); - }, - ); + expect(extensions.length).toEqual(3); + }); }); diff --git a/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts b/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts index fa97d3194e5..168c14d013e 100644 --- a/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts +++ b/packages/thirdweb/src/extensions/dynamic-contracts/write/installPublishedExtension.test.ts @@ -11,53 +11,50 @@ import { sendTransaction } from "../../../transaction/actions/send-transaction.j import { installPublishedExtension } from "./installPublishedExtension.js"; describe.runIf(process.env.TW_SECRET_KEY)("install extension", () => { - it.sequential( - "should install extension to a dynamic contract", - async () => { - await deployCloneFactory({ - account: TEST_ACCOUNT_A, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - }); + it.sequential("should install extension to a dynamic contract", async () => { + await deployCloneFactory({ + account: TEST_ACCOUNT_A, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); - const deployed = await deployPublishedContract({ - account: TEST_ACCOUNT_A, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - contractId: "EvolvingNFT", - contractParams: { - contractURI: "", - defaultAdmin: TEST_ACCOUNT_A.address, - name: "Evolving nft", - royaltyBps: 0n, - royaltyRecipient: TEST_ACCOUNT_A.address, - saleRecipient: TEST_ACCOUNT_A.address, - symbol: "ENFT", - trustedForwarders: [], - }, - }); + const deployed = await deployPublishedContract({ + account: TEST_ACCOUNT_A, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + contractId: "EvolvingNFT", + contractParams: { + contractURI: "", + defaultAdmin: TEST_ACCOUNT_A.address, + name: "Evolving nft", + royaltyBps: 0n, + royaltyRecipient: TEST_ACCOUNT_A.address, + saleRecipient: TEST_ACCOUNT_A.address, + symbol: "ENFT", + trustedForwarders: [], + }, + }); - const contract = getContract({ - address: deployed, - chain: ANVIL_CHAIN, - client: TEST_CLIENT, - }); + const contract = getContract({ + address: deployed, + chain: ANVIL_CHAIN, + client: TEST_CLIENT, + }); - const transaction = installPublishedExtension({ - account: TEST_ACCOUNT_A, - contract, - extensionName: "DirectListingsLogic", - }); + const transaction = installPublishedExtension({ + account: TEST_ACCOUNT_A, + contract, + extensionName: "DirectListingsLogic", + }); - await sendTransaction({ account: TEST_ACCOUNT_A, transaction }); + await sendTransaction({ account: TEST_ACCOUNT_A, transaction }); - const extensions = await readContract({ - contract, - method: resolveMethod("getAllExtensions"), - params: [], - }); + const extensions = await readContract({ + contract, + method: resolveMethod("getAllExtensions"), + params: [], + }); - expect(extensions.length).toEqual(4); - }, - ); + expect(extensions.length).toEqual(4); + }); });