@@ -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
7982export 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
112127function 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+
193237function 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
252314const functions = defineFunctions ( {
0 commit comments