create-stylus 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/templates/base/.gitignore.template.mjs +2 -2
  3. package/templates/base/dist/cli.js +683 -0
  4. package/templates/base/dist/cli.js.map +1 -0
  5. package/templates/base/package.json +2 -1
  6. package/templates/base/packages/nextjs/.gitignore.template.mjs +3 -3
  7. package/templates/base/packages/nextjs/scaffold.config.ts +2 -2
  8. package/templates/base/packages/stylus/.env.example +5 -2
  9. package/templates/base/packages/stylus/.gitignore.template.mjs +3 -3
  10. package/templates/base/packages/stylus/package.json +0 -1
  11. package/templates/base/packages/stylus/scripts/deploy.ts +29 -12
  12. package/templates/base/packages/stylus/scripts/deploy_contract.ts +34 -43
  13. package/templates/base/packages/stylus/scripts/deploy_wrapper.ts +4 -44
  14. package/templates/base/packages/stylus/scripts/export_abi.ts +13 -13
  15. package/templates/base/packages/stylus/scripts/utils/command.ts +18 -31
  16. package/templates/base/packages/stylus/scripts/utils/contract.ts +23 -14
  17. package/templates/base/packages/stylus/scripts/utils/deployment.ts +169 -45
  18. package/templates/base/packages/stylus/scripts/utils/network.ts +27 -7
  19. package/templates/base/packages/stylus/scripts/utils/type.ts +13 -10
  20. package/templates/base/packages/stylus/your-contract/Cargo.lock +35 -18
  21. package/templates/base/packages/stylus/your-contract/Cargo.toml +3 -1
  22. package/templates/base/packages/stylus/your-contract/src/lib.rs +51 -21
  23. package/templates/base/readme.md +207 -42
  24. package/templates/base/yarn.lock +1 -2
  25. package/templates/base/packages/stylus/README.md +0 -263
  26. package/templates/base/packages/stylus/header.png +0 -0
  27. package/templates/base/packages/stylus/scripts/deploy_all_contracts.ts +0 -59
@@ -3,8 +3,9 @@ import * as fs from "fs";
3
3
  import { ethers } from "ethers";
4
4
  import toml from "toml";
5
5
  import prettier from "prettier";
6
- import { ExportConfig } from "./type";
6
+ import { DeploymentData, ExportConfig } from "./type";
7
7
  import { getContractDataFromDeployments } from "./deployment";
8
+ import { Address } from "viem";
8
9
 
9
10
  export const generatedContractComment = `
10
11
  /**
@@ -42,6 +43,7 @@ export function getContractNameFromCargoToml(contractFolder: string): string {
42
43
  export function getExportConfig(
43
44
  contractFolder?: string,
44
45
  contractName?: string,
46
+ chainId?: string,
45
47
  ): ExportConfig {
46
48
  if (!contractFolder) {
47
49
  throw new Error("Contract folder is required");
@@ -53,10 +55,11 @@ export function getExportConfig(
53
55
  const deploymentData = getContractDataFromDeployments(
54
56
  deploymentDir,
55
57
  contractName,
58
+ chainId,
56
59
  );
57
60
  if (!deploymentData) {
58
61
  throw new Error(
59
- `❌ Contract address for '${contractName}' not found in ${deploymentDir}/addresses.json. Please deploy the contract first.`,
62
+ `❌ Contract address for '${contractName}' not found in any chain-specific deployment files in ${deploymentDir}. Please deploy the contract first.`,
60
63
  );
61
64
  }
62
65
 
@@ -64,7 +67,8 @@ export function getExportConfig(
64
67
  contractFolder,
65
68
  contractName,
66
69
  deploymentDir,
67
- contractAddress: deploymentData.address,
70
+ contractAddress: deploymentData.address as Address,
71
+ txHash: deploymentData.txHash,
68
72
  chainId: deploymentData.chainId,
69
73
  };
70
74
  }
@@ -86,19 +90,28 @@ export function generateContractAddress(): string {
86
90
  return wallet.address;
87
91
  }
88
92
 
89
- export function extractDeployedAddress(output: string): string | null {
90
- // Look for the line containing "deployed code at address:"
93
+ export function extractDeploymentInfo(output: string): DeploymentData | null {
94
+ let result: DeploymentData | null = null;
91
95
  const lines = output.split("\n");
92
96
  for (const line of lines) {
93
97
  if (line.includes("deployed code at address:")) {
94
- // Simple approach: just extract the hex address directly
98
+ // Extract the hex address directly
95
99
  const hexMatch = line.match(/(0x[a-fA-F0-9]{40})/);
96
100
  if (hexMatch && hexMatch[1]) {
97
- return hexMatch[1];
101
+ result = { address: hexMatch[1] as Address, txHash: "" };
102
+ }
103
+ }
104
+ if (line.includes("deployment tx hash:")) {
105
+ const txHashMatch = line.match(/(0x[a-fA-F0-9]{64})/);
106
+ if (txHashMatch && txHashMatch[1]) {
107
+ result = {
108
+ address: result?.address as Address,
109
+ txHash: txHashMatch[1],
110
+ };
98
111
  }
99
112
  }
100
113
  }
101
- return null;
114
+ return result;
102
115
  }
103
116
 
104
117
  export function extractGasPriceFromOutput(output: string): string | null {
@@ -109,7 +122,6 @@ export function extractGasPriceFromOutput(output: string): string | null {
109
122
  // eslint-disable-next-line no-control-regex
110
123
  const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, "");
111
124
 
112
- // Extract the value inside quotes after 'gas price:'
113
125
  const match = cleanLine.match(/gas price:\s*"([^"]+)"/);
114
126
  if (match && match[1]) {
115
127
  return match[1];
@@ -123,6 +135,7 @@ export async function generateTsAbi(
123
135
  abiFilePath: string,
124
136
  contractName: string,
125
137
  contractAddress: string,
138
+ txHash: string,
126
139
  chainId: string,
127
140
  ) {
128
141
  const TARGET_DIR = "../nextjs/contracts/";
@@ -134,9 +147,9 @@ export async function generateTsAbi(
134
147
  const extractedAbi = lines.slice(3).join("\n");
135
148
  const abiJson = JSON.parse(extractedAbi);
136
149
 
137
- // Helper to generate the contract entry
138
150
  const newContractEntry = {
139
151
  address: contractAddress,
152
+ txHash: txHash,
140
153
  abi: abiJson,
141
154
  };
142
155
 
@@ -146,7 +159,6 @@ export async function generateTsAbi(
146
159
  'import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";\n\n';
147
160
 
148
161
  if (fs.existsSync(TARGET_FILE)) {
149
- // Read and parse the existing file
150
162
  const fileContent = fs.readFileSync(TARGET_FILE, "utf8");
151
163
  // Extract the deployedContracts object using regex
152
164
  const match = fileContent.match(
@@ -158,14 +170,11 @@ export async function generateTsAbi(
158
170
  }
159
171
  }
160
172
 
161
- // Ensure the chainId exists
162
173
  if (!deployedContractsObj[chainId]) {
163
174
  deployedContractsObj[chainId] = {};
164
175
  }
165
- // Update or insert the contract entry
166
176
  deployedContractsObj[chainId][contractName] = newContractEntry;
167
177
 
168
- // Stringify the object for TypeScript output
169
178
  const contractsString = JSON.stringify(deployedContractsObj, null, 2);
170
179
 
171
180
  const output = `${fileHeader}const deployedContracts = ${contractsString} as const;\n\nexport default deployedContracts satisfies GenericContractsDeclaration;\n`;
@@ -2,8 +2,8 @@ import { config as dotenvConfig } from "dotenv";
2
2
  import * as path from "path";
3
3
  import * as fs from "fs";
4
4
  import { arbitrumNitro } from "../../../nextjs/utils/scaffold-stylus/chain";
5
- import { DeploymentConfig, DeployOptions } from "./type";
6
- import { getChain, getPrivateKey } from "./network";
5
+ import { DeploymentConfig, DeployOptions, DeploymentData } from "./type";
6
+ import { getAccountAddress, getChain, getPrivateKey } from "./network";
7
7
  import { getContractNameFromCargoToml } from "./contract";
8
8
 
9
9
  // Load environment variables from .env file
@@ -42,6 +42,7 @@ export function getDeploymentConfig(
42
42
  }
43
43
 
44
44
  return {
45
+ deployerAddress: getAccountAddress(deployOptions.network),
45
46
  privateKey: getPrivateKey(deployOptions.network),
46
47
  contractFolder: deployOptions.contract!,
47
48
  contractName,
@@ -58,79 +59,202 @@ export function ensureDeploymentDirectory(deploymentDir: string): void {
58
59
  }
59
60
 
60
61
  /**
61
- * Save the deployed contract address to addresses.json in the deployment directory.
62
+ * Save the deployed contract address to <chain.id>_latest.json in the deployment directory.
63
+ * If a latest file already exists, it gets renamed to include a timestamp.
62
64
  * Updates or creates the file, using contractName as the key.
63
65
  */
64
- export function saveDeployedAddress(config: DeploymentConfig) {
66
+ export function saveDeployment(
67
+ config: DeploymentConfig,
68
+ deploymentInfo: DeploymentData,
69
+ ) {
65
70
  try {
66
- const addressesPath = path.resolve(config.deploymentDir, "addresses.json");
67
- let addresses: Record<string, any> = {};
68
- if (fs.existsSync(addressesPath)) {
69
- const content = fs.readFileSync(addressesPath, "utf8");
71
+ const chainId = config.chain?.id || arbitrumNitro.id;
72
+ const networkPath = path.resolve(
73
+ config.deploymentDir,
74
+ `${chainId}_latest.json`,
75
+ );
76
+
77
+ // Check if the latest file exists and contains the same contract name
78
+ let shouldCreateNewFile = false;
79
+ if (fs.existsSync(networkPath)) {
80
+ try {
81
+ const existingDeployments = JSON.parse(
82
+ fs.readFileSync(networkPath, "utf8"),
83
+ );
84
+ if (existingDeployments[config.contractName]) {
85
+ // Contract with same name already exists, create new file
86
+ shouldCreateNewFile = true;
87
+ }
88
+ } catch (e) {
89
+ console.warn(
90
+ `⚠️ Could not parse existing ${chainId}_latest.json, will overwrite. Error: ${e}`,
91
+ );
92
+ }
93
+ }
94
+
95
+ // If we need to create a new file (contract name already exists), backup the current latest file
96
+ if (shouldCreateNewFile) {
97
+ const currentTimestamp = new Date().getTime();
98
+ const backupPath = networkPath.replace(
99
+ "_latest.json",
100
+ `_${currentTimestamp}.json`,
101
+ );
102
+ fs.renameSync(networkPath, backupPath);
103
+ console.log(`📦 Backed up previous deployment to ${backupPath}`);
104
+ }
105
+
106
+ // Read existing deployments or start fresh
107
+ let deployments: Record<string, any> = {};
108
+ if (fs.existsSync(networkPath)) {
109
+ const content = fs.readFileSync(networkPath, "utf8");
70
110
  try {
71
- addresses = JSON.parse(content);
111
+ deployments = JSON.parse(content);
72
112
  } catch (e) {
73
113
  console.warn(
74
- `⚠️ Could not parse existing addresses.json, will overwrite. Error: ${e}`,
114
+ `⚠️ Could not parse existing ${chainId}_latest.json, will overwrite. Error: ${e}`,
75
115
  );
76
116
  }
77
117
  }
78
118
 
79
- // Save both address and chain ID
80
- addresses[config.contractName] = {
81
- address: config.contractAddress || "",
82
- chainId: (config.chain?.id || arbitrumNitro.id).toString(),
119
+ // Save with the new format
120
+ deployments[config.contractName] = {
121
+ address: deploymentInfo.address,
122
+ txHash: deploymentInfo.txHash,
123
+ contract: config.contractFolder,
83
124
  };
84
125
 
85
- fs.writeFileSync(addressesPath, JSON.stringify(addresses, null, 2));
86
- console.log(`💾 Saved deployed address and chain ID to ${addressesPath}`);
126
+ fs.writeFileSync(networkPath, JSON.stringify(deployments, null, 2));
127
+ console.log(`💾 Saved deployed contract to ${networkPath}`);
87
128
  } catch (e) {
88
- console.error(`❌ Failed to save deployed address: ${e}`);
129
+ console.error(`❌ Failed to save deployed contract: ${e}`);
89
130
  }
90
131
  }
91
132
 
92
- export function printDeployedAddresses(deploymentDir: string): void {
93
- const addressesPath = path.resolve(deploymentDir, "addresses.json");
94
- if (fs.existsSync(addressesPath)) {
95
- const addresses = JSON.parse(fs.readFileSync(addressesPath, "utf8"));
96
- console.log(`📦 Deployed contract addresses (${addressesPath}):`);
97
-
98
- // Format the output to show contract name, address, and chain ID clearly
99
- Object.entries(addresses).forEach(([contractName, contractData]) => {
100
- const data = contractData as {
101
- address: string;
102
- chainId: string;
103
- };
104
- console.log(` ${contractName}:`);
105
- console.log(` Address: ${data.address}`);
106
- console.log(` Chain ID: ${data.chainId}`);
107
- });
133
+ export function printDeployedAddresses(
134
+ deploymentDir: string,
135
+ chainId?: string,
136
+ ): void {
137
+ // If chainId is provided, only look for that specific chain's deployment file
138
+ if (chainId) {
139
+ const networkPath = path.resolve(deploymentDir, `${chainId}_latest.json`);
140
+ if (!fs.existsSync(networkPath)) {
141
+ console.log(
142
+ `📦 No deployment file found for chain ${chainId} in ${deploymentDir}`,
143
+ );
144
+ return;
145
+ }
146
+
147
+ try {
148
+ const deployments = JSON.parse(fs.readFileSync(networkPath, "utf8"));
149
+ console.log(
150
+ `📦 Deployed contracts for chain ${chainId} (${networkPath}):`,
151
+ );
152
+
153
+ // Format the output to show contract name, address, and contract folder clearly
154
+ Object.entries(deployments).forEach(([contractName, contractData]) => {
155
+ const data = contractData as {
156
+ address: string;
157
+ txHash: string;
158
+ contract: string;
159
+ };
160
+ console.log(` ${contractName}:`);
161
+ console.log(` Address: ${data.address}`);
162
+ console.log(` Tx Hash: ${data.txHash}`);
163
+ console.log(` Contract: ${data.contract}`);
164
+ });
165
+ } catch (e) {
166
+ console.warn(`⚠️ Could not parse deployment file ${networkPath}: ${e}`);
167
+ }
168
+ return;
108
169
  }
170
+
171
+ // If no chainId provided, look for all chain-specific deployment files
172
+ const files = fs.readdirSync(deploymentDir);
173
+ const deploymentFiles = files.filter((file) => file.endsWith("_latest.json"));
174
+
175
+ if (deploymentFiles.length === 0) {
176
+ console.log(`📦 No deployment files found in ${deploymentDir}`);
177
+ return;
178
+ }
179
+
180
+ deploymentFiles.forEach((file) => {
181
+ const filePath = path.resolve(deploymentDir, file);
182
+ const currentChainId = file.replace("_latest.json", "");
183
+
184
+ try {
185
+ const deployments = JSON.parse(fs.readFileSync(filePath, "utf8"));
186
+ console.log(
187
+ `📦 Deployed contracts for chain ${currentChainId} (${filePath}):`,
188
+ );
189
+
190
+ // Format the output to show contract name, address, and contract folder clearly
191
+ Object.entries(deployments).forEach(([contractName, contractData]) => {
192
+ const data = contractData as {
193
+ address: string;
194
+ txHash: string;
195
+ contract: string;
196
+ };
197
+ console.log(` ${contractName}:`);
198
+ console.log(` Address: ${data.address}`);
199
+ console.log(` Contract: ${data.contract}`);
200
+ });
201
+ } catch (e) {
202
+ console.warn(`⚠️ Could not parse deployment file ${filePath}: ${e}`);
203
+ }
204
+ });
109
205
  }
110
206
 
111
207
  /**
112
- * Reads the deployed contract data from addresses.json in the deployment directory.
208
+ * Reads the deployed contract data from chain-specific deployment files in the deployment directory.
113
209
  * Returns an object with address and chainId for the given contractName, or undefined if not found.
114
210
  */
115
211
  export function getContractDataFromDeployments(
116
212
  deploymentDir: string,
117
213
  contractName: string,
118
- ): { address: string; chainId: string } | undefined {
119
- const addressesPath = path.resolve(deploymentDir, "addresses.json");
120
- if (fs.existsSync(addressesPath)) {
214
+ chainId?: string,
215
+ ): { address: string; txHash: string; chainId: string } | undefined {
216
+ // If chainId is provided, look for that specific chain's deployment file
217
+ if (chainId) {
218
+ const networkPath = path.resolve(deploymentDir, `${chainId}_latest.json`);
219
+ if (fs.existsSync(networkPath)) {
220
+ try {
221
+ const deployments = JSON.parse(fs.readFileSync(networkPath, "utf8"));
222
+ if (deployments[contractName]?.address) {
223
+ return {
224
+ address: deployments[contractName].address,
225
+ txHash: deployments[contractName].txHash,
226
+ chainId: chainId,
227
+ };
228
+ }
229
+ } catch (e) {
230
+ console.warn(
231
+ `⚠️ Could not parse deployment file at ${networkPath}: ${e}`,
232
+ );
233
+ }
234
+ }
235
+ return undefined;
236
+ }
237
+
238
+ // If no chainId provided, search all deployment files
239
+ const files = fs.readdirSync(deploymentDir);
240
+ const deploymentFiles = files.filter((file) => file.endsWith("_latest.json"));
241
+
242
+ for (const file of deploymentFiles) {
243
+ const filePath = path.resolve(deploymentDir, file);
244
+ const currentChainId = file.replace("_latest.json", "");
121
245
  try {
122
- const addresses = JSON.parse(fs.readFileSync(addressesPath, "utf8"));
123
- if (addresses[contractName]?.address) {
124
- return addresses[contractName] as {
125
- address: string;
126
- chainId: string;
246
+ const deployments = JSON.parse(fs.readFileSync(filePath, "utf8"));
247
+ if (deployments[contractName]?.address) {
248
+ return {
249
+ address: deployments[contractName].address,
250
+ txHash: deployments[contractName].txHash,
251
+ chainId: currentChainId,
127
252
  };
128
253
  }
129
254
  } catch (e) {
130
- console.warn(
131
- `⚠️ Could not parse addresses.json at ${addressesPath}: ${e}`,
132
- );
255
+ console.warn(`⚠️ Could not parse deployment file at ${filePath}: ${e}`);
133
256
  }
134
257
  }
258
+
135
259
  return undefined;
136
260
  }
@@ -1,5 +1,5 @@
1
1
  import { arbitrum, arbitrumSepolia } from "viem/chains";
2
- import { Chain } from "viem";
2
+ import { Address, Chain } from "viem";
3
3
  import { arbitrumNitro } from "../../../nextjs/utils/scaffold-stylus/chain";
4
4
  import * as path from "path";
5
5
  import * as fs from "fs";
@@ -25,10 +25,8 @@ export const ALIASES: Record<string, string> = {
25
25
 
26
26
  export function getChain(networkName: string): SupportedNetworkMinimal | null {
27
27
  try {
28
- // Normalize network name to lowercase for case-insensitive lookup
29
28
  const actualNetworkName = ALIASES[networkName.toLowerCase()] || networkName;
30
29
 
31
- // Find the chain in SUPPORTED_NETWORKS (case-insensitive)
32
30
  const chainEntry = Object.entries(SUPPORTED_NETWORKS).find(
33
31
  ([key]) => key.toLowerCase() === actualNetworkName.toLowerCase(),
34
32
  );
@@ -42,7 +40,6 @@ export function getChain(networkName: string): SupportedNetworkMinimal | null {
42
40
  };
43
41
  }
44
42
 
45
- // If not found, show warning with all supported networks
46
43
  const supportedNetworks = Object.keys(SUPPORTED_NETWORKS);
47
44
  console.warn(
48
45
  `⚠️ Network '${networkName}' is not supported. Supported networks: ${supportedNetworks.join(", ")}`,
@@ -59,9 +56,17 @@ export function getPrivateKey(networkName: string): string {
59
56
 
60
57
  switch (actualNetworkName.toLowerCase()) {
61
58
  case "arbitrum":
62
- return process.env["PRIVATE_KEY_MAINNET"] || "";
59
+ if (process.env["PRIVATE_KEY_MAINNET"]) {
60
+ return process.env["PRIVATE_KEY_MAINNET"];
61
+ } else {
62
+ throw new Error("PRIVATE_KEY_MAINNET is not set");
63
+ }
63
64
  case "arbitrumsepolia":
64
- return process.env["PRIVATE_KEY_SEPOLIA"] || "";
65
+ if (process.env["PRIVATE_KEY_SEPOLIA"]) {
66
+ return process.env["PRIVATE_KEY_SEPOLIA"];
67
+ } else {
68
+ throw new Error("PRIVATE_KEY_SEPOLIA is not set");
69
+ }
65
70
  default:
66
71
  return (
67
72
  process.env["PRIVATE_KEY"] ||
@@ -70,6 +75,21 @@ export function getPrivateKey(networkName: string): string {
70
75
  }
71
76
  }
72
77
 
78
+ export const getAccountAddress = (networkName: string): Address | undefined => {
79
+ const actualNetworkName = ALIASES[networkName.toLowerCase()] || networkName;
80
+ switch (actualNetworkName.toLowerCase()) {
81
+ case "arbitrum":
82
+ return process.env["ACCOUNT_ADDRESS_MAINNET"] as Address;
83
+ case "arbitrumsepolia":
84
+ return process.env["ACCOUNT_ADDRESS_SEPOLIA"] as Address;
85
+ default:
86
+ return (
87
+ (process.env["ACCOUNT_ADDRESS"] as Address) ||
88
+ "0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E"
89
+ );
90
+ }
91
+ };
92
+
73
93
  function getRpcUrlFromChain(chain: Chain): string {
74
94
  //Prefer user rpc url from env
75
95
  switch (chain.id) {
@@ -109,4 +129,4 @@ function getAliasFromNetworkName(networkName: string): string {
109
129
  Object.entries(ALIASES).find(([, alias]) => alias === networkName)?.[0] ||
110
130
  networkName
111
131
  );
112
- }
132
+ }
@@ -1,3 +1,5 @@
1
+ import { Address } from "viem";
2
+
1
3
  interface BaseCommandOptions {
2
4
  _: (string | number)[];
3
5
  $0: string;
@@ -6,29 +8,29 @@ interface BaseCommandOptions {
6
8
 
7
9
  export interface DeployCommandOptions
8
10
  extends BaseCommandOptions,
9
- DeployOptions {
10
- all?: boolean;
11
- }
12
-
13
- export interface AdditionalOptions {
14
- isSingleCommand?: boolean;
15
- shouldClearDeploymentDir?: boolean;
16
- }
11
+ DeployOptions {}
17
12
 
18
13
  export interface DeployOptions {
19
14
  contract?: string;
20
15
  name?: string;
16
+ constructorArgs?: string[];
21
17
  network?: string;
22
18
  estimateGas?: boolean;
23
19
  maxFee?: string;
20
+ verify?: boolean;
21
+ }
22
+
23
+ export interface DeploymentData {
24
+ address: Address;
25
+ txHash: string;
24
26
  }
25
27
 
26
28
  export interface DeploymentConfig {
29
+ deployerAddress: Address | undefined;
27
30
  privateKey: string;
28
31
  contractFolder: string;
29
32
  contractName: string;
30
33
  deploymentDir: string;
31
- contractAddress?: string;
32
34
  chain?: SupportedNetworkMinimal;
33
35
  }
34
36
 
@@ -36,7 +38,8 @@ export interface ExportConfig {
36
38
  contractFolder: string;
37
39
  contractName: string;
38
40
  deploymentDir: string;
39
- contractAddress: string | undefined;
41
+ contractAddress: Address;
42
+ txHash: string;
40
43
  chainId: string;
41
44
  }
42
45
 
@@ -382,9 +382,9 @@ dependencies = [
382
382
 
383
383
  [[package]]
384
384
  name = "alloy-sol-macro"
385
- version = "0.8.25"
385
+ version = "0.8.20"
386
386
  source = "registry+https://github.com/rust-lang/crates.io-index"
387
- checksum = "e10ae8e9a91d328ae954c22542415303919aabe976fe7a92eb06db1b68fd59f2"
387
+ checksum = "13f28f2131dc3a7b8e2cda882758ad4d5231ca26281b9861d4b18c700713e2da"
388
388
  dependencies = [
389
389
  "alloy-sol-macro-expander",
390
390
  "alloy-sol-macro-input",
@@ -396,9 +396,9 @@ dependencies = [
396
396
 
397
397
  [[package]]
398
398
  name = "alloy-sol-macro-expander"
399
- version = "0.8.25"
399
+ version = "0.8.20"
400
400
  source = "registry+https://github.com/rust-lang/crates.io-index"
401
- checksum = "83ad5da86c127751bc607c174d6c9fe9b85ef0889a9ca0c641735d77d4f98f26"
401
+ checksum = "1ee2da033256a3b27131c030933eab0460a709fbcc4d4bd57bf9a5650b2441c5"
402
402
  dependencies = [
403
403
  "alloy-sol-macro-input",
404
404
  "const-hex",
@@ -414,14 +414,13 @@ dependencies = [
414
414
 
415
415
  [[package]]
416
416
  name = "alloy-sol-macro-input"
417
- version = "0.8.25"
417
+ version = "0.8.20"
418
418
  source = "registry+https://github.com/rust-lang/crates.io-index"
419
- checksum = "ba3d30f0d3f9ba3b7686f3ff1de9ee312647aac705604417a2f40c604f409a9e"
419
+ checksum = "4e9d9918b0abb632818bf27e2dfb86b209be8433baacf22100b190bbc0904bd4"
420
420
  dependencies = [
421
421
  "const-hex",
422
422
  "dunce",
423
423
  "heck",
424
- "macro-string",
425
424
  "proc-macro2",
426
425
  "quote",
427
426
  "syn 2.0.101",
@@ -2824,17 +2823,6 @@ dependencies = [
2824
2823
  "hashbrown 0.15.3",
2825
2824
  ]
2826
2825
 
2827
- [[package]]
2828
- name = "macro-string"
2829
- version = "0.1.4"
2830
- source = "registry+https://github.com/rust-lang/crates.io-index"
2831
- checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3"
2832
- dependencies = [
2833
- "proc-macro2",
2834
- "quote",
2835
- "syn 2.0.101",
2836
- ]
2837
-
2838
2826
  [[package]]
2839
2827
  name = "md-5"
2840
2828
  version = "0.10.6"
@@ -3076,6 +3064,34 @@ dependencies = [
3076
3064
  "vcpkg",
3077
3065
  ]
3078
3066
 
3067
+ [[package]]
3068
+ name = "openzeppelin-stylus"
3069
+ version = "0.2.0"
3070
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3071
+ checksum = "4a5bdc0165c9dd0628a88cbeb8c059b59fdb301156364c6759d838cbd4a581a2"
3072
+ dependencies = [
3073
+ "alloy-primitives",
3074
+ "alloy-sol-macro",
3075
+ "alloy-sol-macro-expander",
3076
+ "alloy-sol-macro-input",
3077
+ "alloy-sol-types",
3078
+ "keccak-const",
3079
+ "openzeppelin-stylus-proc",
3080
+ "stylus-sdk",
3081
+ ]
3082
+
3083
+ [[package]]
3084
+ name = "openzeppelin-stylus-proc"
3085
+ version = "0.2.2"
3086
+ source = "registry+https://github.com/rust-lang/crates.io-index"
3087
+ checksum = "f592a114a2f3bbfb29303435064b4444ea3d9c4283b667f551a8543e8da8896f"
3088
+ dependencies = [
3089
+ "convert_case",
3090
+ "proc-macro2",
3091
+ "quote",
3092
+ "syn 2.0.101",
3093
+ ]
3094
+
3079
3095
  [[package]]
3080
3096
  name = "option-ext"
3081
3097
  version = "0.2.0"
@@ -5596,6 +5612,7 @@ dependencies = [
5596
5612
  "ethers",
5597
5613
  "eyre",
5598
5614
  "hex",
5615
+ "openzeppelin-stylus",
5599
5616
  "stylus-sdk",
5600
5617
  "tokio",
5601
5618
  ]
@@ -13,6 +13,7 @@ alloy-primitives = "=0.8.20"
13
13
  alloy-sol-types = "=0.8.20"
14
14
  stylus-sdk = "0.9.0"
15
15
  hex = { version = "0.4", default-features = false }
16
+ openzeppelin-stylus = "0.2.0"
16
17
 
17
18
  [dev-dependencies]
18
19
  alloy-primitives = { version = "=0.8.20", features = ["sha3-keccak"] }
@@ -24,7 +25,8 @@ dotenv = "0.15.0"
24
25
 
25
26
  [features]
26
27
  default = ["mini-alloc"]
27
- export-abi = ["stylus-sdk/export-abi"]
28
+ # stylus-sdk/export-abi will be enabled automatically.
29
+ export-abi = ["openzeppelin-stylus/export-abi"]
28
30
  debug = ["stylus-sdk/debug"]
29
31
  mini-alloc = ["stylus-sdk/mini-alloc"]
30
32