@bananapus/omnichain-deployers-v6 0.0.3 → 0.0.5

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/README.md CHANGED
@@ -1,33 +1,116 @@
1
- # nana-omnichain-deployers-v5
1
+ # Juicebox Omnichain Deployers
2
2
 
3
- Deploy Juicebox projects with cross-chain suckers and optional 721 tiers hooks in a single transaction. Acts as a data hook wrapper to allow tax-free cash outs from suckers.
3
+ Deploy Juicebox projects with cross-chain suckers and optional 721 tiers hooks in a single transaction. Acts as a transparent data hook wrapper that gives suckers tax-free cash outs and on-demand mint permission -- without interfering with any custom data hook the project uses.
4
+
5
+ [Docs](https://docs.juicebox.money) | [Discord](https://discord.gg/juicebox)
6
+
7
+ ## Conceptual Overview
8
+
9
+ Launching a cross-chain Juicebox project normally takes several steps: deploy the project, configure rulesets, set up terminals, deploy suckers, and wire up a data hook that exempts suckers from cash out taxes. `JBOmnichainDeployer` collapses all of this into one transaction.
10
+
11
+ It works by inserting itself as the data hook on every ruleset it touches, storing whatever hook the project actually wants in a mapping keyed by `(projectId, rulesetId)`. When the protocol calls data hook functions during payments and cash outs, the deployer:
12
+
13
+ - **Checks if the holder is a sucker** -- if so, returns 0% cash out tax and grants mint permission. This early return means suckers can always bridge tokens without interference, even if the project's real data hook would revert.
14
+ - **Forwards everything else** to the real data hook, or returns default values if none was set.
15
+
16
+ This wrapping is invisible to the project and its users. The project's custom hook (buyback hook, 721 hook, etc.) works exactly as configured.
17
+
18
+ ### How It Works
19
+
20
+ ```mermaid
21
+ sequenceDiagram
22
+ participant Caller
23
+ participant Deployer as JBOmnichainDeployer
24
+ participant Controller as JBController
25
+ participant Registry as JBSuckerRegistry
26
+ participant Owner
27
+
28
+ Caller->>Deployer: launchProjectFor(owner, rulesets, suckers, ...)
29
+ Deployer->>Deployer: _setup() — store real hooks, insert self as data hook
30
+ Deployer->>Controller: launchProjectFor(owner=deployer, ...)
31
+ Controller-->>Deployer: projectId + project NFT
32
+ Deployer->>Registry: deploySuckersFor(projectId, salt)
33
+ Registry-->>Deployer: sucker addresses
34
+ Deployer->>Owner: transfer project NFT
35
+ ```
36
+
37
+ During operation:
38
+
39
+ ```mermaid
40
+ sequenceDiagram
41
+ participant Terminal
42
+ participant Deployer as JBOmnichainDeployer
43
+ participant Registry as JBSuckerRegistry
44
+ participant Hook as Real Data Hook
45
+
46
+ Terminal->>Deployer: beforeCashOutRecordedWith(context)
47
+ Deployer->>Registry: isSuckerOf(projectId, holder)?
48
+ alt Holder is a sucker
49
+ Deployer-->>Terminal: 0% tax (early return)
50
+ else Not a sucker
51
+ Deployer->>Hook: beforeCashOutRecordedWith(context)
52
+ Hook-->>Deployer: taxRate, count, supply, specs
53
+ Deployer-->>Terminal: forward hook response
54
+ end
55
+ ```
56
+
57
+ ### 721 Tiers Hook Integration
58
+
59
+ The `launch721*` and `queue721*` variants deploy a tiered ERC-721 hook alongside the project. The deployer:
60
+
61
+ 1. Deploys the 721 hook via `HOOK_DEPLOYER`
62
+ 2. Converts 721-specific ruleset configs (`JBPayDataHookRulesetConfig`) to standard configs, enforcing `useDataHookForPay = true` and `allowSetCustomToken = false`
63
+ 3. Wraps the 721 hook with itself (as above)
64
+ 4. Transfers hook ownership to the project via `JBOwnable.transferOwnershipToProject()`
65
+
66
+ ### Deterministic Cross-Chain Addresses
67
+
68
+ Sucker deployment salts are hashed with `_msgSender()` before use:
69
+
70
+ ```
71
+ salt = keccak256(abi.encode(userSalt, _msgSender()))
72
+ ```
73
+
74
+ This means:
75
+ - **Same sender + same salt on each chain = same sucker addresses** (deterministic via CREATE2)
76
+ - Different senders can't collide, even with the same salt
77
+ - `salt = bytes32(0)` skips sucker deployment entirely
78
+
79
+ ### Ruleset ID Prediction
80
+
81
+ The deployer stores hook configs keyed by predicted ruleset IDs (`block.timestamp + i`). This works because `JBRulesets` assigns IDs as `latestId >= block.timestamp ? latestId + 1 : block.timestamp`. For new projects, `latestId` starts at 0, so the first ID is always `block.timestamp`.
82
+
83
+ The `queueRulesetsOf` and `queue721RulesetsOf` functions guard against prediction failures by reverting if `latestRulesetIdOf(projectId) >= block.timestamp` (i.e., rulesets were already queued in the same block).
4
84
 
5
85
  ## Architecture
6
86
 
7
87
  | Contract | Description |
8
88
  |----------|-------------|
9
- | `JBOmnichainDeployer` | Deploys projects, rulesets, and suckers. Wraps the project's real data hook to intercept cash outs from suckers (tax-free) and grant suckers mint permission. |
89
+ | `JBOmnichainDeployer` | Deploys projects, rulesets, and suckers. Wraps the project's real data hook to intercept cash outs from suckers (tax-free) and grant suckers mint permission. Implements `IJBRulesetDataHook`, `IERC721Receiver`, `ERC2771Context`, `JBPermissioned`. |
10
90
 
11
91
  ### Supporting Types
12
92
 
13
93
  | Type | Description |
14
94
  |------|-------------|
15
- | `JBDeployerHookConfig` | Per-ruleset config storing the real data hook and its pay/cash-out usage flags. |
16
- | `JBSuckerDeploymentConfig` | Array of `JBSuckerDeployerConfig` plus a `bytes32` salt for deterministic cross-chain addresses. |
17
- | `IJBOmnichainDeployer` | Interface for all deployer entry points. |
18
-
19
- ### How It Works
95
+ | `JBDeployerHookConfig` | Per-ruleset config storing the real data hook address and its `useDataHookForPay`/`useDataHookForCashOut` flags. |
96
+ | `JBSuckerDeploymentConfig` | Wraps an array of `JBSuckerDeployerConfig` with a `bytes32` salt for deterministic cross-chain addresses. |
97
+ | `IJBOmnichainDeployer` | Interface for all deployer entry points and the `dataHookOf` view. |
20
98
 
21
- `JBOmnichainDeployer` temporarily holds the project NFT during deployment, inserts itself as the data hook on all rulesets via `_setup()`, stores the real data hook in `_dataHookOf`, then transfers ownership to the specified owner. As the data hook it:
99
+ ## Install
22
100
 
23
- 1. **Pay** -- Forwards `beforePayRecordedWith` to the real data hook (if set).
24
- 2. **Cash out** -- If the holder is a registered sucker (`SUCKER_REGISTRY.isSuckerOf`), returns 0% tax rate. Otherwise forwards to the real data hook.
25
- 3. **Mint permission** -- Returns `true` for registered suckers, otherwise forwards to the real data hook.
101
+ ```bash
102
+ npm install @bananapus/omnichain-deployers-v6
103
+ ```
26
104
 
27
- ## Install
105
+ If using Forge directly:
28
106
 
29
107
  ```bash
30
- npm install
108
+ forge install Bananapus/nana-omnichain-deployers-v6
109
+ ```
110
+
111
+ Add to `remappings.txt`:
112
+ ```
113
+ @bananapus/omnichain-deployers-v6/=lib/nana-omnichain-deployers-v6/
31
114
  ```
32
115
 
33
116
  ## Develop
@@ -35,6 +118,58 @@ npm install
35
118
  | Command | Description |
36
119
  |---------|-------------|
37
120
  | `forge build` | Compile contracts |
38
- | `forge test` | Run tests |
121
+ | `forge test` | Run unit, integration, and attack tests |
122
+ | `forge test -vvv` | Run tests with full stack traces |
39
123
  | `npm run deploy:mainnets` | Propose mainnet deployment via Sphinx |
40
124
  | `npm run deploy:testnets` | Propose testnet deployment via Sphinx |
125
+
126
+ ### Settings
127
+
128
+ ```toml
129
+ # foundry.toml
130
+ [profile.default]
131
+ solc = '0.8.26'
132
+ evm_version = 'paris'
133
+ optimizer_runs = 100000
134
+
135
+ [fuzz]
136
+ runs = 4096
137
+ ```
138
+
139
+ ## Repository Layout
140
+
141
+ ```
142
+ src/
143
+ JBOmnichainDeployer.sol # Main contract (719 lines)
144
+ interfaces/
145
+ IJBOmnichainDeployer.sol # Public interface
146
+ structs/
147
+ JBDeployerHookConfig.sol # Stored data hook config
148
+ JBSuckerDeploymentConfig.sol # Sucker deployment params
149
+ test/
150
+ JBOmnichainDeployer.t.sol # Unit tests
151
+ JBOmnichainDeployerGuard.t.sol # Ruleset ID prediction tests
152
+ OmnichainDeployerAttacks.t.sol # Adversarial security tests
153
+ script/
154
+ Deploy.s.sol # Sphinx deployment script
155
+ helpers/
156
+ DeployersDeploymentLib.sol # Deployment address helper
157
+ ```
158
+
159
+ ## Permissions
160
+
161
+ | Permission | ID | Required For |
162
+ |------------|-----|-------------|
163
+ | `DEPLOY_SUCKERS` | `JBPermissionIds.DEPLOY_SUCKERS` | `deploySuckersFor` |
164
+ | `QUEUE_RULESETS` | `JBPermissionIds.QUEUE_RULESETS` | `launchRulesetsFor`, `launch721RulesetsFor`, `queueRulesetsOf`, `queue721RulesetsOf` |
165
+ | `SET_TERMINALS` | `JBPermissionIds.SET_TERMINALS` | `launchRulesetsFor`, `launch721RulesetsFor` |
166
+ | `MAP_SUCKER_TOKEN` | `JBPermissionIds.MAP_SUCKER_TOKEN` | Granted to `SUCKER_REGISTRY` globally (projectId=0) at construction |
167
+
168
+ Note: `launchProjectFor` and `launch721ProjectFor` require no permissions -- anyone can launch a project to any owner.
169
+
170
+ ## Risks
171
+
172
+ - **Ruleset ID mismatch**: If `_setup()` predictions are wrong (e.g., due to same-block queuing from another source), the stored hook configs will be keyed to the wrong rulesets. The `queueRulesetsOf` guard prevents this, but `launchProjectFor` relies on `PROJECTS.count()` being accurate at call time.
173
+ - **Reverting real hook**: If the project's real data hook reverts on `beforePayRecordedWith`, payments are blocked. Suckers are immune to this for cash outs (early return), but not for payments.
174
+ - **Hook forwarding is view-only**: The deployer's data hook functions are `view`, so any real hook that requires state changes in `beforePayRecordedWith` or `beforeCashOutRecordedWith` will fail.
175
+ - **Meta-transaction trust**: ERC2771 `_msgSender()` is used for salt hashing. A compromised trusted forwarder could impersonate senders and create suckers at unexpected addresses.
package/SKILLS.md CHANGED
@@ -1,69 +1,147 @@
1
- # nana-omnichain-deployers-v5
1
+ # Juicebox Omnichain Deployers
2
2
 
3
3
  ## Purpose
4
4
 
5
- Convenience contract for deploying Juicebox projects with cross-chain suckers (and optionally 721 tiers hooks) in a single transaction, while wrapping the data hook to allow tax-free cash outs from suckers.
5
+ Single-transaction deployment of Juicebox projects with cross-chain suckers and optional 721 tiers hooks. Wraps the project's data hook to give suckers tax-free cash outs and mint permission without interfering with custom hooks.
6
6
 
7
7
  ## Contracts
8
8
 
9
9
  | Contract | Role |
10
10
  |----------|------|
11
- | `JBOmnichainDeployer` | Project/ruleset/sucker deployment + data hook wrapper for sucker tax exemption. Implements `IJBRulesetDataHook`, `IERC721Receiver`, `ERC2771Context`, `JBPermissioned`. |
11
+ | `JBOmnichainDeployer` | Deploys projects/rulesets/suckers, wraps data hooks for sucker tax exemption. Implements `IJBRulesetDataHook`, `IERC721Receiver`, `ERC2771Context`, `JBPermissioned`. |
12
12
 
13
13
  ## Key Functions
14
14
 
15
- | Function | Contract | What it does |
16
- |----------|----------|--------------|
17
- | `launchProjectFor(...)` | `JBOmnichainDeployer` | Creates a new project with rulesets, terminals, and suckers. Temporarily holds project NFT. |
18
- | `launch721ProjectFor(...)` | `JBOmnichainDeployer` | Same as above but also deploys a 721 tiers hook and transfers its ownership to the project. |
19
- | `launchRulesetsFor(...)` | `JBOmnichainDeployer` | Launches new rulesets for an existing project. Requires `QUEUE_RULESETS` + `SET_TERMINALS` permissions. |
20
- | `launch721RulesetsFor(...)` | `JBOmnichainDeployer` | Launches rulesets with a new 721 tiers hook attached. |
21
- | `queueRulesetsOf(...)` | `JBOmnichainDeployer` | Queues future rulesets. Requires `QUEUE_RULESETS` permission. |
22
- | `queue721RulesetsOf(...)` | `JBOmnichainDeployer` | Queues rulesets with a new 721 tiers hook. |
23
- | `deploySuckersFor(...)` | `JBOmnichainDeployer` | Deploys new suckers for an existing project. Requires `DEPLOY_SUCKERS` permission. |
24
- | `beforePayRecordedWith(...)` | `JBOmnichainDeployer` | Data hook: forwards to real data hook if set. |
25
- | `beforeCashOutRecordedWith(...)` | `JBOmnichainDeployer` | Data hook: returns 0% tax for suckers, otherwise forwards to real hook. |
26
- | `hasMintPermissionFor(...)` | `JBOmnichainDeployer` | Returns `true` for suckers, otherwise forwards to real hook. |
15
+ ### Deployment
16
+
17
+ | Function | What it does |
18
+ |----------|-------------|
19
+ | `launchProjectFor(owner, projectUri, rulesetConfigs, terminalConfigs, memo, suckerConfig, controller)` | Creates a new project with rulesets, terminals, and suckers in one tx. Temporarily holds the project NFT. Returns `(projectId, suckers)`. |
20
+ | `launch721ProjectFor(owner, deployTiersHookConfig, launchProjectConfig, salt, suckerConfig, controller)` | Same as above but also deploys a 721 tiers hook and transfers its ownership to the project. Returns `(projectId, hook, suckers)`. |
21
+ | `launchRulesetsFor(projectId, rulesetConfigs, terminalConfigs, memo, controller)` | Launches new rulesets + terminals for an existing project. Requires `QUEUE_RULESETS` + `SET_TERMINALS`. |
22
+ | `launch721RulesetsFor(projectId, deployTiersHookConfig, launchRulesetsConfig, controller, salt)` | Launches rulesets with a new 721 tiers hook. Requires `QUEUE_RULESETS` + `SET_TERMINALS`. |
23
+ | `queueRulesetsOf(projectId, rulesetConfigs, memo, controller)` | Queues future rulesets. Requires `QUEUE_RULESETS`. Reverts if rulesets were already queued in the same block. |
24
+ | `queue721RulesetsOf(projectId, deployTiersHookConfig, queueRulesetsConfig, controller, salt)` | Queues rulesets with a new 721 tiers hook. Same same-block guard. |
25
+ | `deploySuckersFor(projectId, suckerConfig)` | Deploys new suckers for an existing project. Requires `DEPLOY_SUCKERS`. |
26
+
27
+ ### Data Hook (IJBRulesetDataHook)
28
+
29
+ | Function | What it does |
30
+ |----------|-------------|
31
+ | `beforePayRecordedWith(context)` | Forwards to the stored real data hook if set and `useDataHookForPay` is true. Otherwise returns the original weight. |
32
+ | `beforeCashOutRecordedWith(context)` | If holder is a sucker: returns 0% tax immediately (never calls real hook). Otherwise forwards to the real hook, or returns original values if none set. |
33
+ | `hasMintPermissionFor(projectId, ruleset, addr)` | Returns `true` for registered suckers. Otherwise forwards to real hook, or returns `false` if none set. |
34
+
35
+ ### Views
36
+
37
+ | Function | What it does |
38
+ |----------|-------------|
39
+ | `dataHookOf(projectId, rulesetId)` | Returns the stored `(useDataHookForPay, useDataHookForCashOut, dataHook)` for a given project and ruleset. |
40
+ | `supportsInterface(interfaceId)` | Returns `true` for `IJBOmnichainDeployer`, `IJBRulesetDataHook`, `IERC721Receiver`, `IERC165`. |
41
+ | `onERC721Received(...)` | Accepts project NFTs from `PROJECTS` only. Reverts for any other NFT contract. |
27
42
 
28
43
  ## Integration Points
29
44
 
30
45
  | Dependency | Import | Used For |
31
46
  |------------|--------|----------|
32
- | `nana-core-v6` | `IJBController`, `JBPermissioned`, `IJBProjects` | Launching projects, permission checks, project NFT transfers |
33
- | `nana-721-hook-v6` | `IJB721TiersHookDeployer`, `JBDeploy721TiersHookConfig` | Deploying 721 tiers hooks for projects |
34
- | `nana-suckers-v6` | `IJBSuckerRegistry` | Deploying suckers, checking `isSuckerOf` for tax-free cash outs |
47
+ | `nana-core-v6` | `IJBController`, `JBPermissioned`, `IJBProjects`, `IJBRulesetDataHook` | Launching projects, permission checks, project NFT transfers, data hook interface |
48
+ | `nana-721-hook-v6` | `IJB721TiersHookDeployer`, `JBDeploy721TiersHookConfig`, `JBLaunchProjectConfig`, `JBPayDataHookRulesetConfig` | Deploying 721 tiers hooks, converting 721 configs to standard configs |
49
+ | `nana-suckers-v6` | `IJBSuckerRegistry` | Deploying suckers, checking `isSuckerOf()` for tax-free cash outs |
35
50
  | `nana-ownable-v6` | `JBOwnable` | Transferring 721 hook ownership to the project |
36
- | `nana-permission-ids-v6` | `JBPermissionIds` | Permission constants (`DEPLOY_SUCKERS`, `QUEUE_RULESETS`, `SET_TERMINALS`, `MAP_SUCKER_TOKEN`) |
51
+ | `nana-permission-ids-v6` | `JBPermissionIds` | Permission constants |
37
52
  | `@openzeppelin/contracts` | `ERC2771Context`, `IERC721Receiver` | Meta-transaction support, receiving project NFTs |
38
53
 
39
54
  ## Key Types
40
55
 
41
- | Struct/Enum | Key Fields | Used In |
42
- |-------------|------------|---------|
56
+ | Struct | Key Fields | Used In |
57
+ |--------|------------|---------|
43
58
  | `JBDeployerHookConfig` | `bool useDataHookForPay`, `bool useDataHookForCashOut`, `IJBRulesetDataHook dataHook` | `_dataHookOf` mapping keyed by `(projectId, rulesetId)` |
44
- | `JBSuckerDeploymentConfig` | `JBSuckerDeployerConfig[] deployerConfigurations`, `bytes32 salt` | All launch/deploy functions |
59
+ | `JBSuckerDeploymentConfig` | `JBSuckerDeployerConfig[] deployerConfigurations`, `bytes32 salt` | All launch and deploy functions |
60
+
61
+ ## Permission IDs
62
+
63
+ | Permission | Used By |
64
+ |------------|---------|
65
+ | `DEPLOY_SUCKERS` | `deploySuckersFor` -- deploy new suckers for a project |
66
+ | `QUEUE_RULESETS` | `launchRulesetsFor`, `launch721RulesetsFor`, `queueRulesetsOf`, `queue721RulesetsOf` -- modify project rulesets |
67
+ | `SET_TERMINALS` | `launchRulesetsFor`, `launch721RulesetsFor` -- set terminal configurations |
68
+ | `MAP_SUCKER_TOKEN` | Granted to `SUCKER_REGISTRY` at construction with `projectId=0` (all projects) |
69
+
70
+ ## Errors
71
+
72
+ | Error | When |
73
+ |-------|------|
74
+ | `JBOmnichainDeployer_InvalidHook` | `_setup()` detects `rulesetConfig.metadata.dataHook == address(this)` -- prevents infinite forwarding loops |
75
+ | `JBOmnichainDeployer_UnexpectedNFTReceived` | `onERC721Received` called by a contract other than `PROJECTS` |
76
+ | `JBOmnichainDeployer_RulesetIdsUnpredictable` | `queueRulesetsOf`/`queue721RulesetsOf` called when `latestRulesetIdOf(projectId) >= block.timestamp` -- ruleset ID prediction would fail |
77
+ | `JBOmnichainDeployer_ProjectIdMismatch` | `launchProjectFor`/`launch721ProjectFor` -- the project ID returned by the controller does not match the predicted `PROJECTS.count() + 1` |
78
+ | `JBOmnichainDeployer_ControllerMismatch` | `launchRulesetsFor`/`launch721RulesetsFor`/`queueRulesetsOf`/`queue721RulesetsOf` -- the provided controller does not match the project's controller in `JBDirectory` |
45
79
 
46
80
  ## Gotchas
47
81
 
48
- - The deployer inserts itself as the data hook via `_setup()`. Setting a ruleset's data hook to `address(this)` reverts with `JBOmnichainDeployer_InvalidHook` to prevent infinite forwarding loops.
49
- - Ruleset IDs in `_dataHookOf` are keyed by `block.timestamp + i`, which must match the IDs assigned during controller launch.
50
- - Sucker deployment salts are hashed with `_msgSender()` to prevent cross-deployer address collisions.
51
- - Constructor grants `MAP_SUCKER_TOKEN` permission to `SUCKER_REGISTRY` with `projectId=0` (wildcard for all projects).
52
- - Only accepts ERC-721 transfers from the `PROJECTS` contract (the `onERC721Received` callback reverts otherwise).
82
+ 1. `launchProjectFor` and `launch721ProjectFor` require **no permissions** -- anyone can launch a project to any owner address.
83
+ 2. `queueRulesetsOf` and `queue721RulesetsOf` **revert if called in the same block** as a previous ruleset queue (whether via deployer or directly). The `launch*` functions don't have this guard because they predict IDs from `PROJECTS.count()`, which is always 0 for a new project.
84
+ 3. Ruleset IDs in `_dataHookOf` are keyed by `block.timestamp + i`. If the controller assigns different IDs than predicted, the stored hook config will be orphaned and the deployer will behave as if no hook was set (returning default values).
85
+ 4. Sucker deployment salts are hashed with `_msgSender()`: `keccak256(abi.encode(salt, _msgSender()))`. Cross-chain deterministic addresses require using the **same sender** on each chain. For `launch721ProjectFor`, the 721 hook salt uses `keccak256(abi.encode(_msgSender(), salt))` (reversed order).
86
+ 5. `salt = bytes32(0)` **skips sucker deployment entirely**. Use a nonzero salt to deploy suckers.
87
+ 6. The deployer **always forces `useDataHookForCashOut = true`** on every ruleset it touches, even if the original config had it as `false`. This is required so the deployer can intercept cash outs to check for suckers.
88
+ 7. Suckers get an **early return** in `beforeCashOutRecordedWith` -- they bypass the real data hook entirely. This means suckers can cash out even if the real hook would revert.
89
+ 8. If no real data hook is stored (or `address(0)`), `hasMintPermissionFor` returns `false` for non-suckers. It does **not** return the default `true`.
90
+ 9. 721 ruleset config conversion enforces `useDataHookForPay = true` and `allowSetCustomToken = false`. These cannot be overridden.
91
+ 10. Hook ownership is transferred to the **project** (not the owner) via `JBOwnable.transferOwnershipToProject(projectId)`. The project owner controls the hook through project ownership.
92
+ 11. The deployer holds the project NFT temporarily during launch. If the controller's `launchProjectFor` reverts, the entire transaction reverts -- no stuck NFTs.
93
+ 12. The constructor grants `MAP_SUCKER_TOKEN` permission to `SUCKER_REGISTRY` with `projectId=0`, meaning the registry can map tokens for **any project** deployed through this deployer.
94
+ 13. All data hook functions (`beforePayRecordedWith`, `beforeCashOutRecordedWith`, `hasMintPermissionFor`) are `view`. If the project's real hook needs to modify state in these functions, it will fail.
95
+ 14. Setting a ruleset's `dataHook` to `address(this)` (the deployer itself) reverts with `JBOmnichainDeployer_InvalidHook`. This prevents infinite forwarding loops.
96
+ 15. `onERC721Received` only accepts NFTs from the `PROJECTS` contract. Sending any other ERC-721 to the deployer will revert.
97
+ 16. ERC2771 meta-transaction support allows gasless deployments via a trusted forwarder. Salt hashing uses `_msgSender()` (not `msg.sender`), so forwarder-relayed transactions use the original sender's address for deterministic sucker addresses.
53
98
 
54
99
  ## Example Integration
55
100
 
56
101
  ```solidity
57
102
  import {IJBOmnichainDeployer} from "@bananapus/omnichain-deployers-v6/src/interfaces/IJBOmnichainDeployer.sol";
103
+ import {JBSuckerDeploymentConfig} from "@bananapus/omnichain-deployers-v6/src/structs/JBSuckerDeploymentConfig.sol";
104
+ import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
105
+ import {JBRulesetConfig} from "@bananapus/core-v6/src/structs/JBRulesetConfig.sol";
106
+ import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.sol";
107
+ import {JBSuckerDeployerConfig} from "@bananapus/suckers-v6/src/structs/JBSuckerDeployerConfig.sol";
58
108
 
59
- // Launch a project with suckers in one transaction
109
+ // --- Launch a project with suckers ---
110
+
111
+ // Configure sucker deployment (use a nonzero salt to deploy suckers).
112
+ JBSuckerDeploymentConfig memory suckerConfig = JBSuckerDeploymentConfig({
113
+ deployerConfigurations: suckerDeployerConfigs, // per-chain sucker configs
114
+ salt: bytes32("my-project-salt") // deterministic addresses
115
+ });
116
+
117
+ // Launch in one transaction.
60
118
  (uint256 projectId, address[] memory suckers) = omnichainDeployer.launchProjectFor({
61
119
  owner: msg.sender,
62
- projectUri: "ipfs://...",
120
+ projectUri: "ipfs://project-metadata",
63
121
  rulesetConfigurations: rulesetConfigs,
64
122
  terminalConfigurations: terminalConfigs,
65
- memo: "Launching with suckers",
123
+ memo: "Launching omnichain project",
66
124
  suckerDeploymentConfiguration: suckerConfig,
67
125
  controller: controller
68
126
  });
127
+
128
+ // --- Add suckers to an existing project ---
129
+
130
+ // Requires DEPLOY_SUCKERS permission on the project.
131
+ address[] memory newSuckers = omnichainDeployer.deploySuckersFor({
132
+ projectId: projectId,
133
+ suckerDeploymentConfiguration: suckerConfig
134
+ });
135
+
136
+ // --- Queue new rulesets with a 721 hook ---
137
+
138
+ // Requires QUEUE_RULESETS permission. Must be called in a different block
139
+ // than any previous ruleset queue for this project.
140
+ (uint256 rulesetId, IJB721TiersHook hook) = omnichainDeployer.queue721RulesetsOf({
141
+ projectId: projectId,
142
+ deployTiersHookConfig: tiersHookConfig,
143
+ queueRulesetsConfig: queueConfig,
144
+ controller: controller,
145
+ salt: bytes32("my-hook-salt")
146
+ });
69
147
  ```
package/foundry.toml CHANGED
@@ -1,7 +1,7 @@
1
1
  [profile.default]
2
2
  solc = '0.8.26'
3
3
  evm_version = 'paris'
4
- optimizer_runs = 100000
4
+ optimizer_runs = 200
5
5
  libs = ["node_modules", "lib"]
6
6
  fs_permissions = [{ access = "read-write", path = "./"}]
7
7
 
package/package.json CHANGED
@@ -1,23 +1,27 @@
1
1
  {
2
2
  "name": "@bananapus/omnichain-deployers-v6",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/Bananapus/nana-omnichain-deployers-v6"
8
8
  },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
9
12
  "scripts": {
10
13
  "test": "forge test",
14
+ "coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
11
15
  "deploy:mainnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
12
16
  "deploy:testnets": "source ./.env && export START_TIME=$(date +%s) && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
13
17
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-omnichain-deployers-v6'"
14
18
  },
15
19
  "dependencies": {
16
- "@bananapus/721-hook-v6": "^0.0.7",
17
- "@bananapus/core-v6": "^0.0.5",
18
- "@bananapus/suckers-v6": "^0.0.4",
19
- "@bananapus/ownable-v6": "^0.0.4",
20
- "@bananapus/permission-ids-v6": "^0.0.3",
20
+ "@bananapus/721-hook-v6": "^0.0.9",
21
+ "@bananapus/core-v6": "^0.0.10",
22
+ "@bananapus/suckers-v6": "^0.0.7",
23
+ "@bananapus/ownable-v6": "^0.0.6",
24
+ "@bananapus/permission-ids-v6": "^0.0.5",
21
25
  "@openzeppelin/contracts": "^5.2.0"
22
26
  },
23
27
  "devDependencies": {