@bananapus/omnichain-deployers-v6 0.0.25 → 0.0.26

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/omnichain-deployers-v6",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -129,6 +129,246 @@ contract JBOmnichainDeployer is
129
129
  PERMISSIONS.setPermissionsFor({account: address(this), permissionsData: permissionData});
130
130
  }
131
131
 
132
+ //*********************************************************************//
133
+ // ---------------------- external transactions ---------------------- //
134
+ //*********************************************************************//
135
+
136
+ /// @notice Deploy new suckers for an existing project.
137
+ /// @dev Only the juicebox's owner or an operator with `JBPermissionIds.DEPLOY_SUCKERS` can call this entrypoint.
138
+ /// The downstream registry call also maps the configured tokens on each newly created sucker, so the same
139
+ /// end-to-end operation depends on the project's token-mapping authority being arranged for the registry.
140
+ /// @param projectId The ID of the project to deploy suckers for.
141
+ /// @param suckerDeploymentConfiguration The suckers to set up for the project.
142
+ function deploySuckersFor(
143
+ uint256 projectId,
144
+ JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration
145
+ )
146
+ external
147
+ override
148
+ returns (address[] memory suckers)
149
+ {
150
+ // Enforce permissions.
151
+ _requirePermissionFrom({
152
+ account: PROJECTS.ownerOf(projectId), projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS
153
+ });
154
+
155
+ // Deploy the suckers.
156
+ // Note: the salt includes `_msgSender()` for replay protection. Cross-chain deterministic
157
+ // address matching requires using the same sender address on each chain.
158
+ // slither-disable-next-line unused-return
159
+ suckers = SUCKER_REGISTRY.deploySuckersFor({
160
+ projectId: projectId,
161
+ salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
162
+ configurations: suckerDeploymentConfiguration.deployerConfigurations
163
+ });
164
+ }
165
+
166
+ /// @notice Creates a project with a 721 tiers hook attached and with suckers.
167
+ /// @param owner The address to set as the owner of the project. The ERC-721 which confers this project's ownership
168
+ /// will be sent to this address.
169
+ /// @param projectUri The project's metadata URI.
170
+ /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
171
+ /// @param rulesetConfigurations The rulesets to queue. Custom data hooks are read from each ruleset's metadata.
172
+ /// @param terminalConfigurations The terminals to set up for the project.
173
+ /// @param memo A memo to pass along to the emitted event.
174
+ /// @param suckerDeploymentConfiguration The suckers to set up for the project. Suckers facilitate cross-chain
175
+ /// token transfers between peer projects on different networks.
176
+ /// @param controller The controller to use for launching the project.
177
+ /// @return projectId The ID of the newly launched project.
178
+ /// @return hook The 721 tiers hook that was deployed for the project.
179
+ /// @return suckers The addresses of the deployed suckers.
180
+ function launchProjectFor(
181
+ address owner,
182
+ string calldata projectUri,
183
+ JBOmnichain721Config calldata deploy721Config,
184
+ JBRulesetConfig[] memory rulesetConfigurations,
185
+ JBTerminalConfig[] calldata terminalConfigurations,
186
+ string calldata memo,
187
+ JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
188
+ IJBController controller
189
+ )
190
+ external
191
+ override
192
+ returns (uint256 projectId, IJB721TiersHook hook, address[] memory suckers)
193
+ {
194
+ return _launchProjectFor({
195
+ owner: owner,
196
+ projectUri: projectUri,
197
+ deploy721Config: deploy721Config,
198
+ rulesetConfigurations: rulesetConfigurations,
199
+ terminalConfigurations: terminalConfigurations,
200
+ memo: memo,
201
+ suckerDeploymentConfiguration: suckerDeploymentConfiguration,
202
+ controller: controller
203
+ });
204
+ }
205
+
206
+ /// @notice Creates a project with a default (empty-tier) 721 hook and with suckers.
207
+ /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config.
208
+ /// @param owner The address to set as the owner of the project.
209
+ /// @param projectUri The project's metadata URI.
210
+ /// @param rulesetConfigurations The rulesets to queue.
211
+ /// @param terminalConfigurations The terminals to set up for the project.
212
+ /// @param memo A memo to pass along to the emitted event.
213
+ /// @param suckerDeploymentConfiguration The suckers to set up for the project.
214
+ /// @param controller The controller to use for launching the project.
215
+ /// @return projectId The ID of the newly launched project.
216
+ /// @return hook The 721 tiers hook that was deployed for the project.
217
+ /// @return suckers The addresses of the deployed suckers.
218
+ function launchProjectFor(
219
+ address owner,
220
+ string calldata projectUri,
221
+ JBRulesetConfig[] memory rulesetConfigurations,
222
+ JBTerminalConfig[] calldata terminalConfigurations,
223
+ string calldata memo,
224
+ JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
225
+ IJBController controller
226
+ )
227
+ external
228
+ override
229
+ returns (uint256 projectId, IJB721TiersHook hook, address[] memory suckers)
230
+ {
231
+ return _launchProjectFor({
232
+ owner: owner,
233
+ projectUri: projectUri,
234
+ deploy721Config: _default721Config(rulesetConfigurations),
235
+ rulesetConfigurations: rulesetConfigurations,
236
+ terminalConfigurations: terminalConfigurations,
237
+ memo: memo,
238
+ suckerDeploymentConfiguration: suckerDeploymentConfiguration,
239
+ controller: controller
240
+ });
241
+ }
242
+
243
+ /// @notice Launches new rulesets for a project with a 721 tiers hook attached, using this contract as the data
244
+ /// hook.
245
+ /// @param projectId The ID of the project to launch the rulesets for.
246
+ /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
247
+ /// @param rulesetConfigurations The rulesets to launch. Custom data hooks are read from each ruleset's metadata.
248
+ /// @param terminalConfigurations The terminals to set up for the project.
249
+ /// @param memo A memo to pass along to the emitted event.
250
+ /// @param controller The controller to use for launching the rulesets.
251
+ /// @return rulesetId The ID of the newly launched rulesets.
252
+ /// @return hook The 721 tiers hook that was deployed for the project.
253
+ function launchRulesetsFor(
254
+ uint256 projectId,
255
+ JBOmnichain721Config memory deploy721Config,
256
+ JBRulesetConfig[] memory rulesetConfigurations,
257
+ JBTerminalConfig[] calldata terminalConfigurations,
258
+ string calldata memo,
259
+ IJBController controller
260
+ )
261
+ external
262
+ override
263
+ returns (uint256 rulesetId, IJB721TiersHook hook)
264
+ {
265
+ return _launchRulesetsFor({
266
+ projectId: projectId,
267
+ deploy721Config: deploy721Config,
268
+ rulesetConfigurations: rulesetConfigurations,
269
+ terminalConfigurations: terminalConfigurations,
270
+ memo: memo,
271
+ controller: controller
272
+ });
273
+ }
274
+
275
+ /// @notice Launches new rulesets for a project with a default (empty-tier) 721 hook.
276
+ /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config.
277
+ /// @param projectId The ID of the project to launch the rulesets for.
278
+ /// @param rulesetConfigurations The rulesets to launch.
279
+ /// @param terminalConfigurations The terminals to set up for the project.
280
+ /// @param memo A memo to pass along to the emitted event.
281
+ /// @param controller The controller to use for launching the rulesets.
282
+ /// @return rulesetId The ID of the newly launched rulesets.
283
+ /// @return hook The 721 tiers hook that was deployed for the project.
284
+ function launchRulesetsFor(
285
+ uint256 projectId,
286
+ JBRulesetConfig[] memory rulesetConfigurations,
287
+ JBTerminalConfig[] calldata terminalConfigurations,
288
+ string calldata memo,
289
+ IJBController controller
290
+ )
291
+ external
292
+ override
293
+ returns (uint256 rulesetId, IJB721TiersHook hook)
294
+ {
295
+ return _launchRulesetsFor({
296
+ projectId: projectId,
297
+ deploy721Config: _default721Config(rulesetConfigurations),
298
+ rulesetConfigurations: rulesetConfigurations,
299
+ terminalConfigurations: terminalConfigurations,
300
+ memo: memo,
301
+ controller: controller
302
+ });
303
+ }
304
+
305
+ /// @dev Make sure this contract can only receive project NFTs from `JBProjects`.
306
+ function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
307
+ // Make sure the 721 received is from the `JBProjects` contract.
308
+ if (msg.sender != address(PROJECTS)) revert JBOmnichainDeployer_UnexpectedNFTReceived();
309
+
310
+ return IERC721Receiver.onERC721Received.selector;
311
+ }
312
+
313
+ /// @notice Queues new rulesets for a project with a 721 tiers hook attached, using this contract as the data hook.
314
+ /// @dev If `deploy721Config.deployTiersHookConfig.tiersConfig.tiers.length > 0`, a new 721 hook is deployed.
315
+ /// Otherwise, the 721 hook from the latest ruleset is carried forward.
316
+ /// @param projectId The ID of the project to queue the rulesets for.
317
+ /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
318
+ /// @param rulesetConfigurations The rulesets to queue. Custom data hooks are read from each ruleset's metadata.
319
+ /// @param memo A memo to pass along to the emitted event.
320
+ /// @param controller The controller to use for queuing the rulesets.
321
+ /// @return rulesetId The ID of the newly queued rulesets.
322
+ /// @return hook The 721 tiers hook (newly deployed or carried forward from the previous ruleset).
323
+ function queueRulesetsOf(
324
+ uint256 projectId,
325
+ JBOmnichain721Config memory deploy721Config,
326
+ JBRulesetConfig[] memory rulesetConfigurations,
327
+ string calldata memo,
328
+ IJBController controller
329
+ )
330
+ external
331
+ override
332
+ returns (uint256 rulesetId, IJB721TiersHook hook)
333
+ {
334
+ return _queueRulesetsOf({
335
+ projectId: projectId,
336
+ deploy721Config: deploy721Config,
337
+ rulesetConfigurations: rulesetConfigurations,
338
+ memo: memo,
339
+ controller: controller
340
+ });
341
+ }
342
+
343
+ /// @notice Queues new rulesets for a project with a default (empty-tier) 721 hook, carrying forward the existing
344
+ /// hook.
345
+ /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config. With 0 tiers in
346
+ /// the default config, the existing hook is always carried forward.
347
+ /// @param projectId The ID of the project to queue the rulesets for.
348
+ /// @param rulesetConfigurations The rulesets to queue.
349
+ /// @param memo A memo to pass along to the emitted event.
350
+ /// @param controller The controller to use for queuing the rulesets.
351
+ /// @return rulesetId The ID of the newly queued rulesets.
352
+ /// @return hook The 721 tiers hook carried forward from the previous ruleset.
353
+ function queueRulesetsOf(
354
+ uint256 projectId,
355
+ JBRulesetConfig[] memory rulesetConfigurations,
356
+ string calldata memo,
357
+ IJBController controller
358
+ )
359
+ external
360
+ override
361
+ returns (uint256 rulesetId, IJB721TiersHook hook)
362
+ {
363
+ return _queueRulesetsOf({
364
+ projectId: projectId,
365
+ deploy721Config: _default721Config(rulesetConfigurations),
366
+ rulesetConfigurations: rulesetConfigurations,
367
+ memo: memo,
368
+ controller: controller
369
+ });
370
+ }
371
+
132
372
  //*********************************************************************//
133
373
  // ------------------------- external views -------------------------- //
134
374
  //*********************************************************************//
@@ -171,7 +411,9 @@ contract JBOmnichainDeployer is
171
411
  // Compute the cross-chain surplus: local surplus + sum of known peer chain surpluses.
172
412
  // This prevents disproportionate reclaim when tokens bridge away but surplus stays.
173
413
  effectiveSurplusValue = context.surplus.value
174
- + SUCKER_REGISTRY.remoteSurplusOf(context.projectId, 18, uint256(uint160(context.surplus.token)));
414
+ + SUCKER_REGISTRY.remoteSurplusOf({
415
+ projectId: context.projectId, decimals: 18, currency: uint256(uint160(context.surplus.token))
416
+ });
175
417
 
176
418
  // Will hold the 721 hook's cash out specifications (always 0 or 1 element).
177
419
  JBCashOutHookSpecification[] memory tiered721HookSpecifications;
@@ -278,385 +520,122 @@ contract JBOmnichainDeployer is
278
520
 
279
521
  // The amount entering the project after tier splits.
280
522
  uint256 projectAmount = totalSplitAmount >= context.amount.value ? 0 : context.amount.value - totalSplitAmount;
281
-
282
- // Get the custom data hook's weight and specs. Reduce the amount so it only considers funds entering the
283
- // project, and pass the 721 hook's weight so the data hook sees the split-adjusted weight.
284
- JBPayHookSpecification[] memory dataHookSpecs;
285
- bool customHookCalled;
286
- {
287
- JBDeployerHookConfig memory extraHook = _extraDataHookOf[context.projectId][context.rulesetId];
288
- if (address(extraHook.dataHook) != address(0) && extraHook.useDataHookForPay) {
289
- JBBeforePayRecordedContext memory hookContext = context;
290
- hookContext.amount.value = projectAmount;
291
- // Pass the 721 hook's weight (which accounts for split deductions) so the data hook
292
- // makes its decisions (e.g. mint-vs-swap) based on the correct post-split weight.
293
- if (has721Hook) hookContext.weight = tiered721Weight;
294
- (weight, dataHookSpecs) = extraHook.dataHook.beforePayRecordedWith(hookContext);
295
- customHookCalled = true;
296
- }
297
- }
298
-
299
- if (!customHookCalled) {
300
- // Use the 721 hook's weight directly (already scaled for splits) or fall back to context weight.
301
- weight = has721Hook ? tiered721Weight : context.weight;
302
- }
303
-
304
- // Merge specifications: 721 hook spec first, then data hook specs.
305
- bool hasDataHookSpecs = dataHookSpecs.length > 0;
306
- if (!hasTiered721Spec && !hasDataHookSpecs) return (weight, hookSpecifications);
307
-
308
- hookSpecifications = new JBPayHookSpecification[]((hasTiered721Spec ? 1 : 0) + dataHookSpecs.length);
309
-
310
- uint256 specIndex;
311
- if (hasTiered721Spec) hookSpecifications[specIndex++] = tiered721HookSpec;
312
- for (uint256 i; i < dataHookSpecs.length; i++) {
313
- hookSpecifications[specIndex + i] = dataHookSpecs[i];
314
- }
315
- }
316
-
317
- /// @notice Get the extra data hook for a project and ruleset.
318
- /// @param projectId The ID of the project to get the extra data hook for.
319
- /// @param rulesetId The ID of the ruleset to get the extra data hook for.
320
- /// @return hook The extra data hook configured for the project/ruleset.
321
- function extraDataHookOf(
322
- uint256 projectId,
323
- uint256 rulesetId
324
- )
325
- external
326
- view
327
- override
328
- returns (JBDeployerHookConfig memory hook)
329
- {
330
- return _extraDataHookOf[projectId][rulesetId];
331
- }
332
-
333
- /// @notice A flag indicating whether an address has permission to mint a project's tokens on-demand.
334
- /// @dev A project's data hook can allow any address to mint its tokens.
335
- /// @param projectId The ID of the project whose token can be minted.
336
- /// @param ruleset The ruleset to check the token minting permission of.
337
- /// @param addr The address to check the token minting permission of.
338
- /// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
339
- function hasMintPermissionFor(
340
- uint256 projectId,
341
- JBRuleset memory ruleset,
342
- address addr
343
- )
344
- external
345
- view
346
- override
347
- returns (bool)
348
- {
349
- // Suckers always get mint permission.
350
- if (SUCKER_REGISTRY.isSuckerOf({projectId: projectId, addr: addr})) return true;
351
-
352
- // Check the extra data hook (the 721 hook doesn't grant mint permission).
353
- JBDeployerHookConfig memory extraHook = _extraDataHookOf[projectId][ruleset.id];
354
- if (address(extraHook.dataHook) != address(0)) {
355
- if (extraHook.dataHook.hasMintPermissionFor({projectId: projectId, ruleset: ruleset, addr: addr})) {
356
- return true;
357
- }
358
- }
359
-
360
- return false;
361
- }
362
-
363
- /// @notice Get the tiered 721 hook config for a project and ruleset.
364
- /// @param projectId The ID of the project to get the 721 hook for.
365
- /// @param rulesetId The ID of the ruleset to get the 721 hook for.
366
- /// @return hook The 721 tiers hook.
367
- /// @return useDataHookForCashOut Whether the 721 hook is used for cash outs.
368
- function tiered721HookOf(
369
- uint256 projectId,
370
- uint256 rulesetId
371
- )
372
- external
373
- view
374
- override
375
- returns (IJB721TiersHook hook, bool useDataHookForCashOut)
376
- {
377
- JBTiered721HookConfig memory config = _tiered721HookOf[projectId][rulesetId];
378
- return (config.hook, config.useDataHookForCashOut);
379
- }
380
-
381
- //*********************************************************************//
382
- // -------------------------- public views --------------------------- //
383
- //*********************************************************************//
384
-
385
- /// @notice Indicates if this contract adheres to the specified interface.
386
- /// @dev See `IERC165.supportsInterface`.
387
- /// @return A flag indicating if the provided interface ID is supported.
388
- function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
389
- return interfaceId == type(IJBOmnichainDeployer).interfaceId
390
- || interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IERC721Receiver).interfaceId
391
- || interfaceId == type(IERC165).interfaceId;
392
- }
393
-
394
- //*********************************************************************//
395
- // --------------------- external transactions ----------------------- //
396
- //*********************************************************************//
397
-
398
- /// @notice Deploy new suckers for an existing project.
399
- /// @dev Only the juicebox's owner or an operator with `JBPermissionIds.DEPLOY_SUCKERS` can call this entrypoint.
400
- /// The downstream registry call also maps the configured tokens on each newly created sucker, so the same
401
- /// end-to-end operation depends on the project's token-mapping authority being arranged for the registry.
402
- /// @param projectId The ID of the project to deploy suckers for.
403
- /// @param suckerDeploymentConfiguration The suckers to set up for the project.
404
- function deploySuckersFor(
405
- uint256 projectId,
406
- JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration
407
- )
408
- external
409
- override
410
- returns (address[] memory suckers)
411
- {
412
- // Enforce permissions.
413
- _requirePermissionFrom({
414
- account: PROJECTS.ownerOf(projectId), projectId: projectId, permissionId: JBPermissionIds.DEPLOY_SUCKERS
415
- });
416
-
417
- // Deploy the suckers.
418
- // Note: the salt includes `_msgSender()` for replay protection. Cross-chain deterministic
419
- // address matching requires using the same sender address on each chain.
420
- // slither-disable-next-line unused-return
421
- suckers = SUCKER_REGISTRY.deploySuckersFor({
422
- projectId: projectId,
423
- salt: keccak256(abi.encode(suckerDeploymentConfiguration.salt, _msgSender())),
424
- configurations: suckerDeploymentConfiguration.deployerConfigurations
425
- });
426
- }
427
-
428
- /// @notice Creates a project with a 721 tiers hook attached and with suckers.
429
- /// @param owner The address to set as the owner of the project. The ERC-721 which confers this project's ownership
430
- /// will be sent to this address.
431
- /// @param projectUri The project's metadata URI.
432
- /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
433
- /// @param rulesetConfigurations The rulesets to queue. Custom data hooks are read from each ruleset's metadata.
434
- /// @param terminalConfigurations The terminals to set up for the project.
435
- /// @param memo A memo to pass along to the emitted event.
436
- /// @param suckerDeploymentConfiguration The suckers to set up for the project. Suckers facilitate cross-chain
437
- /// token transfers between peer projects on different networks.
438
- /// @param controller The controller to use for launching the project.
439
- /// @return projectId The ID of the newly launched project.
440
- /// @return hook The 721 tiers hook that was deployed for the project.
441
- /// @return suckers The addresses of the deployed suckers.
442
- function launchProjectFor(
443
- address owner,
444
- string calldata projectUri,
445
- JBOmnichain721Config calldata deploy721Config,
446
- JBRulesetConfig[] memory rulesetConfigurations,
447
- JBTerminalConfig[] calldata terminalConfigurations,
448
- string calldata memo,
449
- JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
450
- IJBController controller
451
- )
452
- external
453
- override
454
- returns (uint256 projectId, IJB721TiersHook hook, address[] memory suckers)
455
- {
456
- return _launchProjectFor({
457
- owner: owner,
458
- projectUri: projectUri,
459
- deploy721Config: deploy721Config,
460
- rulesetConfigurations: rulesetConfigurations,
461
- terminalConfigurations: terminalConfigurations,
462
- memo: memo,
463
- suckerDeploymentConfiguration: suckerDeploymentConfiguration,
464
- controller: controller
465
- });
466
- }
467
-
468
- /// @notice Creates a project with a default (empty-tier) 721 hook and with suckers.
469
- /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config.
470
- /// @param owner The address to set as the owner of the project.
471
- /// @param projectUri The project's metadata URI.
472
- /// @param rulesetConfigurations The rulesets to queue.
473
- /// @param terminalConfigurations The terminals to set up for the project.
474
- /// @param memo A memo to pass along to the emitted event.
475
- /// @param suckerDeploymentConfiguration The suckers to set up for the project.
476
- /// @param controller The controller to use for launching the project.
477
- /// @return projectId The ID of the newly launched project.
478
- /// @return hook The 721 tiers hook that was deployed for the project.
479
- /// @return suckers The addresses of the deployed suckers.
480
- function launchProjectFor(
481
- address owner,
482
- string calldata projectUri,
483
- JBRulesetConfig[] memory rulesetConfigurations,
484
- JBTerminalConfig[] calldata terminalConfigurations,
485
- string calldata memo,
486
- JBSuckerDeploymentConfig calldata suckerDeploymentConfiguration,
487
- IJBController controller
488
- )
489
- external
490
- override
491
- returns (uint256 projectId, IJB721TiersHook hook, address[] memory suckers)
492
- {
493
- return _launchProjectFor({
494
- owner: owner,
495
- projectUri: projectUri,
496
- deploy721Config: _default721Config(rulesetConfigurations),
497
- rulesetConfigurations: rulesetConfigurations,
498
- terminalConfigurations: terminalConfigurations,
499
- memo: memo,
500
- suckerDeploymentConfiguration: suckerDeploymentConfiguration,
501
- controller: controller
502
- });
523
+
524
+ // Get the custom data hook's weight and specs. Reduce the amount so it only considers funds entering the
525
+ // project, and pass the 721 hook's weight so the data hook sees the split-adjusted weight.
526
+ JBPayHookSpecification[] memory dataHookSpecs;
527
+ bool customHookCalled;
528
+ {
529
+ JBDeployerHookConfig memory extraHook = _extraDataHookOf[context.projectId][context.rulesetId];
530
+ if (address(extraHook.dataHook) != address(0) && extraHook.useDataHookForPay) {
531
+ JBBeforePayRecordedContext memory hookContext = context;
532
+ hookContext.amount.value = projectAmount;
533
+ // Pass the 721 hook's weight (which accounts for split deductions) so the data hook
534
+ // makes its decisions (e.g. mint-vs-swap) based on the correct post-split weight.
535
+ if (has721Hook) hookContext.weight = tiered721Weight;
536
+ (weight, dataHookSpecs) = extraHook.dataHook.beforePayRecordedWith(hookContext);
537
+ customHookCalled = true;
538
+ }
539
+ }
540
+
541
+ if (!customHookCalled) {
542
+ // Use the 721 hook's weight directly (already scaled for splits) or fall back to context weight.
543
+ weight = has721Hook ? tiered721Weight : context.weight;
544
+ }
545
+
546
+ // Merge specifications: 721 hook spec first, then data hook specs.
547
+ bool hasDataHookSpecs = dataHookSpecs.length > 0;
548
+ if (!hasTiered721Spec && !hasDataHookSpecs) return (weight, hookSpecifications);
549
+
550
+ hookSpecifications = new JBPayHookSpecification[]((hasTiered721Spec ? 1 : 0) + dataHookSpecs.length);
551
+
552
+ uint256 specIndex;
553
+ if (hasTiered721Spec) hookSpecifications[specIndex++] = tiered721HookSpec;
554
+ for (uint256 i; i < dataHookSpecs.length; i++) {
555
+ hookSpecifications[specIndex + i] = dataHookSpecs[i];
556
+ }
503
557
  }
504
558
 
505
- /// @notice Launches new rulesets for a project with a 721 tiers hook attached, using this contract as the data
506
- /// hook.
507
- /// @param projectId The ID of the project to launch the rulesets for.
508
- /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
509
- /// @param rulesetConfigurations The rulesets to launch. Custom data hooks are read from each ruleset's metadata.
510
- /// @param terminalConfigurations The terminals to set up for the project.
511
- /// @param memo A memo to pass along to the emitted event.
512
- /// @param controller The controller to use for launching the rulesets.
513
- /// @return rulesetId The ID of the newly launched rulesets.
514
- /// @return hook The 721 tiers hook that was deployed for the project.
515
- function launchRulesetsFor(
559
+ /// @notice Get the extra data hook for a project and ruleset.
560
+ /// @param projectId The ID of the project to get the extra data hook for.
561
+ /// @param rulesetId The ID of the ruleset to get the extra data hook for.
562
+ /// @return hook The extra data hook configured for the project/ruleset.
563
+ function extraDataHookOf(
516
564
  uint256 projectId,
517
- JBOmnichain721Config memory deploy721Config,
518
- JBRulesetConfig[] memory rulesetConfigurations,
519
- JBTerminalConfig[] calldata terminalConfigurations,
520
- string calldata memo,
521
- IJBController controller
565
+ uint256 rulesetId
522
566
  )
523
567
  external
568
+ view
524
569
  override
525
- returns (uint256 rulesetId, IJB721TiersHook hook)
570
+ returns (JBDeployerHookConfig memory hook)
526
571
  {
527
- return _launchRulesetsFor({
528
- projectId: projectId,
529
- deploy721Config: deploy721Config,
530
- rulesetConfigurations: rulesetConfigurations,
531
- terminalConfigurations: terminalConfigurations,
532
- memo: memo,
533
- controller: controller
534
- });
572
+ return _extraDataHookOf[projectId][rulesetId];
535
573
  }
536
574
 
537
- /// @notice Launches new rulesets for a project with a default (empty-tier) 721 hook.
538
- /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config.
539
- /// @param projectId The ID of the project to launch the rulesets for.
540
- /// @param rulesetConfigurations The rulesets to launch.
541
- /// @param terminalConfigurations The terminals to set up for the project.
542
- /// @param memo A memo to pass along to the emitted event.
543
- /// @param controller The controller to use for launching the rulesets.
544
- /// @return rulesetId The ID of the newly launched rulesets.
545
- /// @return hook The 721 tiers hook that was deployed for the project.
546
- function launchRulesetsFor(
575
+ /// @notice A flag indicating whether an address has permission to mint a project's tokens on-demand.
576
+ /// @dev A project's data hook can allow any address to mint its tokens.
577
+ /// @param projectId The ID of the project whose token can be minted.
578
+ /// @param ruleset The ruleset to check the token minting permission of.
579
+ /// @param addr The address to check the token minting permission of.
580
+ /// @return flag A flag indicating whether the address has permission to mint the project's tokens on-demand.
581
+ function hasMintPermissionFor(
547
582
  uint256 projectId,
548
- JBRulesetConfig[] memory rulesetConfigurations,
549
- JBTerminalConfig[] calldata terminalConfigurations,
550
- string calldata memo,
551
- IJBController controller
583
+ JBRuleset memory ruleset,
584
+ address addr
552
585
  )
553
586
  external
587
+ view
554
588
  override
555
- returns (uint256 rulesetId, IJB721TiersHook hook)
589
+ returns (bool)
556
590
  {
557
- return _launchRulesetsFor({
558
- projectId: projectId,
559
- deploy721Config: _default721Config(rulesetConfigurations),
560
- rulesetConfigurations: rulesetConfigurations,
561
- terminalConfigurations: terminalConfigurations,
562
- memo: memo,
563
- controller: controller
564
- });
565
- }
566
-
567
- /// @dev Make sure this contract can only receive project NFTs from `JBProjects`.
568
- function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
569
- // Make sure the 721 received is from the `JBProjects` contract.
570
- if (msg.sender != address(PROJECTS)) revert JBOmnichainDeployer_UnexpectedNFTReceived();
591
+ // Suckers always get mint permission.
592
+ if (SUCKER_REGISTRY.isSuckerOf({projectId: projectId, addr: addr})) return true;
571
593
 
572
- return IERC721Receiver.onERC721Received.selector;
573
- }
594
+ // Check the extra data hook (the 721 hook doesn't grant mint permission).
595
+ JBDeployerHookConfig memory extraHook = _extraDataHookOf[projectId][ruleset.id];
596
+ if (address(extraHook.dataHook) != address(0)) {
597
+ if (extraHook.dataHook.hasMintPermissionFor({projectId: projectId, ruleset: ruleset, addr: addr})) {
598
+ return true;
599
+ }
600
+ }
574
601
 
575
- /// @notice Queues new rulesets for a project with a 721 tiers hook attached, using this contract as the data hook.
576
- /// @dev If `deploy721Config.deployTiersHookConfig.tiersConfig.tiers.length > 0`, a new 721 hook is deployed.
577
- /// Otherwise, the 721 hook from the latest ruleset is carried forward.
578
- /// @param projectId The ID of the project to queue the rulesets for.
579
- /// @param deploy721Config The 721 hook deployment config (hook config + cash-out flag + salt).
580
- /// @param rulesetConfigurations The rulesets to queue. Custom data hooks are read from each ruleset's metadata.
581
- /// @param memo A memo to pass along to the emitted event.
582
- /// @param controller The controller to use for queuing the rulesets.
583
- /// @return rulesetId The ID of the newly queued rulesets.
584
- /// @return hook The 721 tiers hook (newly deployed or carried forward from the previous ruleset).
585
- function queueRulesetsOf(
586
- uint256 projectId,
587
- JBOmnichain721Config memory deploy721Config,
588
- JBRulesetConfig[] memory rulesetConfigurations,
589
- string calldata memo,
590
- IJBController controller
591
- )
592
- external
593
- override
594
- returns (uint256 rulesetId, IJB721TiersHook hook)
595
- {
596
- return _queueRulesetsOf({
597
- projectId: projectId,
598
- deploy721Config: deploy721Config,
599
- rulesetConfigurations: rulesetConfigurations,
600
- memo: memo,
601
- controller: controller
602
- });
602
+ return false;
603
603
  }
604
604
 
605
- /// @notice Queues new rulesets for a project with a default (empty-tier) 721 hook, carrying forward the existing
606
- /// hook.
607
- /// @dev Uses `baseCurrency` from the first ruleset and `decimals = 18` for the default 721 config. With 0 tiers in
608
- /// the default config, the existing hook is always carried forward.
609
- /// @param projectId The ID of the project to queue the rulesets for.
610
- /// @param rulesetConfigurations The rulesets to queue.
611
- /// @param memo A memo to pass along to the emitted event.
612
- /// @param controller The controller to use for queuing the rulesets.
613
- /// @return rulesetId The ID of the newly queued rulesets.
614
- /// @return hook The 721 tiers hook carried forward from the previous ruleset.
615
- function queueRulesetsOf(
605
+ /// @notice Get the tiered 721 hook config for a project and ruleset.
606
+ /// @param projectId The ID of the project to get the 721 hook for.
607
+ /// @param rulesetId The ID of the ruleset to get the 721 hook for.
608
+ /// @return hook The 721 tiers hook.
609
+ /// @return useDataHookForCashOut Whether the 721 hook is used for cash outs.
610
+ function tiered721HookOf(
616
611
  uint256 projectId,
617
- JBRulesetConfig[] memory rulesetConfigurations,
618
- string calldata memo,
619
- IJBController controller
612
+ uint256 rulesetId
620
613
  )
621
614
  external
615
+ view
622
616
  override
623
- returns (uint256 rulesetId, IJB721TiersHook hook)
617
+ returns (IJB721TiersHook hook, bool useDataHookForCashOut)
624
618
  {
625
- return _queueRulesetsOf({
626
- projectId: projectId,
627
- deploy721Config: _default721Config(rulesetConfigurations),
628
- rulesetConfigurations: rulesetConfigurations,
629
- memo: memo,
630
- controller: controller
631
- });
619
+ JBTiered721HookConfig memory config = _tiered721HookOf[projectId][rulesetId];
620
+ return (config.hook, config.useDataHookForCashOut);
632
621
  }
633
622
 
634
623
  //*********************************************************************//
635
- // -------------------------- internal views ------------------------ //
636
- //*********************************************************************//
637
-
638
- //*********************************************************************//
639
- // ------------------------ internal functions ----------------------- //
624
+ // -------------------------- public views --------------------------- //
640
625
  //*********************************************************************//
641
626
 
642
- /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
643
- function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
644
- return ERC2771Context._contextSuffixLength();
627
+ /// @notice Indicates if this contract adheres to the specified interface.
628
+ /// @dev See `IERC165.supportsInterface`.
629
+ /// @return A flag indicating if the provided interface ID is supported.
630
+ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
631
+ return interfaceId == type(IJBOmnichainDeployer).interfaceId
632
+ || interfaceId == type(IJBRulesetDataHook).interfaceId || interfaceId == type(IERC721Receiver).interfaceId
633
+ || interfaceId == type(IERC165).interfaceId;
645
634
  }
646
635
 
647
- /// @notice Returns a default `JBOmnichain721Config` with `currency` from the first ruleset's `baseCurrency`,
648
- /// `decimals = 18`, empty tiers, no cash-out handling, and no salt.
649
- /// @param rulesetConfigurations The ruleset configurations to derive defaults from.
650
- /// @return config The default 721 config.
651
- function _default721Config(JBRulesetConfig[] memory rulesetConfigurations)
652
- internal
653
- pure
654
- returns (JBOmnichain721Config memory config)
655
- {
656
- if (rulesetConfigurations.length == 0) revert JBOmnichainDeployer_NoRulesetConfigurations();
657
- config.deployTiersHookConfig.tiersConfig.currency = rulesetConfigurations[0].metadata.baseCurrency;
658
- config.deployTiersHookConfig.tiersConfig.decimals = 18;
659
- }
636
+ //*********************************************************************//
637
+ // ---------------------- internal transactions ---------------------- //
638
+ //*********************************************************************//
660
639
 
661
640
  /// @notice Deploys a 721 tiers hook for a project.
662
641
  /// @dev The caller is responsible for transferring ownership to the project via
@@ -782,18 +761,6 @@ contract JBOmnichainDeployer is
782
761
  });
783
762
  }
784
763
 
785
- /// @notice The calldata. Preferred to use over `msg.data`.
786
- /// @return calldata The `msg.data` of this call.
787
- function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
788
- return ERC2771Context._msgData();
789
- }
790
-
791
- /// @notice The message's sender. Preferred to use over `msg.sender`.
792
- /// @return sender The address which sent this call.
793
- function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
794
- return ERC2771Context._msgSender();
795
- }
796
-
797
764
  /// @notice Internal implementation of `queueRulesetsOf`.
798
765
  function _queueRulesetsOf(
799
766
  uint256 projectId,
@@ -914,6 +881,41 @@ contract JBOmnichainDeployer is
914
881
  return rulesetConfigurations;
915
882
  }
916
883
 
884
+ //*********************************************************************//
885
+ // ----------------------- internal views ---------------------------- //
886
+ //*********************************************************************//
887
+
888
+ /// @dev ERC-2771 specifies the context as being a single address (20 bytes).
889
+ function _contextSuffixLength() internal view virtual override(ERC2771Context, Context) returns (uint256) {
890
+ return ERC2771Context._contextSuffixLength();
891
+ }
892
+
893
+ /// @notice Returns a default `JBOmnichain721Config` with `currency` from the first ruleset's `baseCurrency`,
894
+ /// `decimals = 18`, empty tiers, no cash-out handling, and no salt.
895
+ /// @param rulesetConfigurations The ruleset configurations to derive defaults from.
896
+ /// @return config The default 721 config.
897
+ function _default721Config(JBRulesetConfig[] memory rulesetConfigurations)
898
+ internal
899
+ pure
900
+ returns (JBOmnichain721Config memory config)
901
+ {
902
+ if (rulesetConfigurations.length == 0) revert JBOmnichainDeployer_NoRulesetConfigurations();
903
+ config.deployTiersHookConfig.tiersConfig.currency = rulesetConfigurations[0].metadata.baseCurrency;
904
+ config.deployTiersHookConfig.tiersConfig.decimals = 18;
905
+ }
906
+
907
+ /// @notice The calldata. Preferred to use over `msg.data`.
908
+ /// @return calldata The `msg.data` of this call.
909
+ function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
910
+ return ERC2771Context._msgData();
911
+ }
912
+
913
+ /// @notice The message's sender. Preferred to use over `msg.sender`.
914
+ /// @return sender The address which sent this call.
915
+ function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
916
+ return ERC2771Context._msgSender();
917
+ }
918
+
917
919
  /// @notice Validates that the provided controller matches the project's controller in the directory.
918
920
  /// @dev The reflexive lookup (controller.DIRECTORY().controllerOf()) is intentional — it confirms the
919
921
  /// caller-provided controller is the one the directory recognizes for this project, preventing a