Skip to content

Commit a0d368a

Browse files
committed
feat: add erc721 consecutive extension
1 parent 3b4f8f4 commit a0d368a

Some content is hidden

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

46 files changed

+823
-290
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@openzeppelin/wizard-common": patch
3+
---
4+
5+
Cairo: ERC721 consecutive description for AI prompts.

packages/common/src/ai/descriptions/cairo.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export const cairoERC721Descriptions = {
5858
baseUri: 'A base uri for the non-fungible token.',
5959
enumerable:
6060
'Whether to allow on-chain enumeration of all tokens or those owned by an account. Increases gas cost of transfers.',
61+
consecutive:
62+
'Whether to enable batch minting of consecutive token IDs during construction. CAUTION: ERC721 extensions that implement custom balanceOf logic, such as ERC721Consecutive, interfere with enumerability and should not be used together with ERC721Enumerable.',
6163
votes:
6264
'Whether to keep track of individual units for voting in on-chain governance. Voting durations can be expressed as block numbers or timestamps.',
6365
};

packages/core/cairo_alpha/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Add ERC721Consecutive extension ([#765](https://github.com/OpenZeppelin/contracts-wizard/pull/765))
6+
7+
- **Breaking changes**:
8+
- Use OpenZeppelin Contracts for Cairo v4.0.0-alpha.0.
9+
310
## 3.0.0 (2026-01-10)
411

512
- Add support for `with_components` macro. ([#703](https://github.com/OpenZeppelin/contracts-wizard/pull/703))

packages/core/cairo_alpha/ava.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
extensions: ['ts'],
33
require: ['ts-node/register'],
4+
files: ['src/**/*.test.ts'],
45
watchmode: {
56
ignoreChanges: ['contracts', 'artifacts', 'cache'],
67
},

packages/core/cairo_alpha/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@openzeppelin/wizard-cairo-alpha",
33
"private": true,
4-
"version": "3.0.0",
4+
"version": "4.0.0-alpha.0",
55
"description": "A boilerplate generator to get started with the latest alpha version of OpenZeppelin Contracts for Cairo",
66
"license": "AGPL-3.0-only",
77
"repository": "https://github.com/OpenZeppelin/contracts-wizard",

packages/core/cairo_alpha/src/erc721.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const defaults: Required<ERC721Options> = {
2626
pausable: false,
2727
mintable: false,
2828
enumerable: false,
29+
consecutive: false,
2930
votes: false,
3031
royaltyInfo: royaltyInfoDefaults,
3132
appName: '', // Defaults to empty string, but user must provide a non-empty value if votes are enabled
@@ -48,6 +49,7 @@ export interface ERC721Options extends CommonContractOptions {
4849
pausable?: boolean;
4950
mintable?: boolean;
5051
enumerable?: boolean;
52+
consecutive?: boolean;
5153
votes?: boolean;
5254
royaltyInfo?: RoyaltyInfoOptions;
5355
appName?: string;
@@ -63,6 +65,7 @@ function withDefaults(opts: ERC721Options): Required<ERC721Options> {
6365
pausable: opts.pausable ?? defaults.pausable,
6466
mintable: opts.mintable ?? defaults.mintable,
6567
enumerable: opts.enumerable ?? defaults.enumerable,
68+
consecutive: opts.consecutive ?? defaults.consecutive,
6669
royaltyInfo: opts.royaltyInfo ?? defaults.royaltyInfo,
6770
votes: opts.votes ?? defaults.votes,
6871
appName: opts.appName ?? defaults.appName,
@@ -78,6 +81,14 @@ export function isAccessControlRequired(opts: Partial<ERC721Options>): boolean {
7881

7982
export function buildERC721(opts: ERC721Options): Contract {
8083
const allOpts = withDefaults(opts);
84+
85+
if (allOpts.consecutive && allOpts.enumerable) {
86+
throw new OptionsError({
87+
consecutive:
88+
'ERC721 extensions that implement custom balanceOf logic, such as ERC721Consecutive, interfere with enumerability and should not be used together with ERC721Enumerable.',
89+
});
90+
}
91+
8192
const c = new ContractBuilder(allOpts.name, allOpts.macros);
8293

8394
addBase(c, toByteArray(allOpts.name), toByteArray(allOpts.symbol), toByteArray(allOpts.baseUri));
@@ -99,6 +110,10 @@ export function buildERC721(opts: ERC721Options): Contract {
99110
addEnumerable(c);
100111
}
101112

113+
if (allOpts.consecutive) {
114+
addConsecutive(c);
115+
}
116+
102117
setAccessControl(c, allOpts.access);
103118
setUpgradeable(c, allOpts.upgradeable, allOpts.access);
104119
setInfo(c, allOpts.info);
@@ -110,7 +125,7 @@ export function buildERC721(opts: ERC721Options): Contract {
110125
}
111126

112127
function addHooks(c: ContractBuilder, opts: Required<ERC721Options>) {
113-
const usesCustomHooks = opts.pausable || opts.enumerable || opts.votes;
128+
const usesCustomHooks = opts.pausable || opts.enumerable || opts.consecutive || opts.votes;
114129
if (usesCustomHooks) {
115130
const ERC721HooksTrait: BaseImplementedTrait = {
116131
name: `ERC721HooksImpl`,
@@ -121,7 +136,7 @@ function addHooks(c: ContractBuilder, opts: Required<ERC721Options>) {
121136
c.addImplementedTrait(ERC721HooksTrait);
122137
c.addUseClause('starknet', 'ContractAddress');
123138

124-
const requiresMutState = opts.enumerable || opts.votes;
139+
const requiresMutState = opts.enumerable || opts.consecutive || opts.votes;
125140
const initStateLine = requiresMutState
126141
? 'let mut contract_state = self.get_contract_mut()'
127142
: 'let contract_state = self.get_contract()';
@@ -132,6 +147,9 @@ function addHooks(c: ContractBuilder, opts: Required<ERC721Options>) {
132147
if (opts.enumerable) {
133148
beforeUpdateCode.push('contract_state.erc721_enumerable.before_update(to, token_id)');
134149
}
150+
if (opts.consecutive) {
151+
beforeUpdateCode.push('contract_state.erc721_consecutive.before_update(to, token_id, auth)');
152+
}
135153
if (opts.votes) {
136154
if (!opts.appName) {
137155
throw new OptionsError({
@@ -167,6 +185,27 @@ function addHooks(c: ContractBuilder, opts: Required<ERC721Options>) {
167185
],
168186
code: beforeUpdateCode,
169187
});
188+
189+
// Add after_update hook for consecutive
190+
if (opts.consecutive) {
191+
const afterUpdateCode = [
192+
'let mut contract_state = self.get_contract_mut()',
193+
'contract_state.erc721_consecutive.after_update(to, token_id, auth)',
194+
];
195+
c.addFunction(ERC721HooksTrait, {
196+
name: 'after_update',
197+
args: [
198+
{
199+
name: 'ref self',
200+
type: `ERC721Component::ComponentState<ContractState>`,
201+
},
202+
{ name: 'to', type: 'ContractAddress' },
203+
{ name: 'token_id', type: 'u256' },
204+
{ name: 'auth', type: 'ContractAddress' },
205+
],
206+
code: afterUpdateCode,
207+
});
208+
}
170209
} else {
171210
c.addUseClause('openzeppelin_token::erc721', 'ERC721HooksEmptyImpl');
172211
}
@@ -190,6 +229,11 @@ function addEnumerable(c: ContractBuilder) {
190229
c.addComponent(components.ERC721EnumerableComponent, [], true);
191230
}
192231

232+
function addConsecutive(c: ContractBuilder) {
233+
c.addComponent(components.ERC721ConsecutiveComponent, [], true);
234+
c.addUseClause('openzeppelin_token::erc721::extensions::erc721_consecutive', 'DefaultConfig');
235+
}
236+
193237
function addBurnable(c: ContractBuilder) {
194238
c.addUseClause('core::num::traits', 'Zero');
195239
c.addUseClause('starknet', 'get_caller_address');
@@ -247,6 +291,24 @@ const components = defineComponents({
247291
},
248292
],
249293
},
294+
ERC721ConsecutiveComponent: {
295+
path: 'openzeppelin_token::erc721::extensions',
296+
substorage: {
297+
name: 'erc721_consecutive',
298+
type: 'ERC721ConsecutiveComponent::Storage',
299+
},
300+
event: {
301+
name: 'ERC721ConsecutiveEvent',
302+
type: 'ERC721ConsecutiveComponent::Event',
303+
},
304+
impls: [
305+
{
306+
name: 'ERC721ConsecutiveInternalImpl',
307+
embed: false,
308+
value: 'ERC721ConsecutiveComponent::InternalImpl<ContractState>',
309+
},
310+
],
311+
},
250312
});
251313

252314
const functions = defineFunctions({

packages/core/cairo_alpha/src/generate/erc721.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function prepareBlueprint(opts: GeneratorOptions) {
2424
baseUri: ['https://example.com/'],
2525
burnable: booleans,
2626
enumerable: booleans,
27+
consecutive: booleans,
2728
votes: booleans,
2829
appName: ['MyApp'],
2930
appVersion: ['v1'],

0 commit comments

Comments
 (0)