@bananapus/buyback-hook-v6 0.0.5 → 0.0.7

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,16 +1,16 @@
1
- # nana-buyback-hook-v5
1
+ # Juicebox Buyback Hook
2
2
 
3
- A Juicebox data hook and pay hook that automatically routes payments through the better of two paths: minting new project tokens from the terminal, or buying them from a Uniswap V3 pool -- whichever yields more tokens for the beneficiary. The project's reserved rate applies to either route.
3
+ A Juicebox data hook and pay hook that automatically routes payments through the better of two paths: minting new project tokens from the terminal, or buying them from a Uniswap V4 pool -- whichever yields more tokens for the beneficiary. The project's reserved rate applies to either route.
4
4
 
5
- _If you're having trouble understanding this contract, take a look at the [core protocol contracts](https://github.com/Bananapus/nana-core) and the [documentation](https://docs.juicebox.money/) first. If you have questions, reach out on [Discord](https://discord.com/invite/ErQYmth4dS)._
5
+ _If you're having trouble understanding this contract, take a look at the [core protocol contracts](https://github.com/Bananapus/nana-core-v6) and the [documentation](https://docs.juicebox.money/) first. If you have questions, reach out on [Discord](https://discord.com/invite/ErQYmth4dS)._
6
6
 
7
7
  ## Architecture
8
8
 
9
9
  | Contract | Description |
10
10
  |----------|-------------|
11
- | `JBBuybackHook` | Core hook. Implements `IJBRulesetDataHook` (checked before recording payment) and `IJBPayHook` (executed after). Compares the terminal's mint rate against a Uniswap V3 swap quote and takes the better route. Swapped tokens are burned, then re-minted through the controller to apply the reserved rate uniformly. |
12
- | `JBBuybackHookRegistry` | A proxy data hook that delegates `beforePayRecordedWith` to a per-project or default `JBBuybackHook` instance. Allows project owners to choose (and lock) which buyback hook implementation they use. |
13
- | `JBSwapLib` | Shared library for slippage tolerance and price limit calculations. Uses a continuous sigmoid formula for smooth dynamic slippage across all swap sizes. |
11
+ | `JBBuybackHook` | Core hook. Implements `IJBRulesetDataHook` (checked before recording payment), `IJBPayHook` (executed after), and `IUnlockCallback` (Uniswap V4 swap settlement). Compares the terminal's mint rate against a Uniswap V4 swap quote and takes the better route. Swapped tokens are burned, then re-minted through the controller to apply the reserved rate uniformly. |
12
+ | `JBBuybackHookRegistry` | A proxy data hook that delegates `beforePayRecordedWith` to a per-project or default `JBBuybackHook` instance. The registry owner manages an allowlist of hook implementations. Project owners choose (and can permanently lock) which buyback hook their project uses. |
13
+ | `JBSwapLib` | Shared library for oracle queries, slippage tolerance, price impact estimation, and `sqrtPriceLimitX96` calculations. Uses a continuous sigmoid formula for smooth dynamic slippage across all swap sizes. |
14
14
 
15
15
  ## How It Works
16
16
 
@@ -18,45 +18,57 @@ _If you're having trouble understanding this contract, take a look at the [core
18
18
  sequenceDiagram
19
19
  participant Terminal
20
20
  participant Buyback hook
21
- participant Uniswap pool
21
+ participant V4 PoolManager
22
22
  participant Controller
23
23
  Note right of Terminal: User calls pay(...) to pay the project
24
24
  Terminal->>Buyback hook: Calls beforePayRecordedWith(...) with payment data
25
25
  Buyback hook->>Terminal: If swap is better: weight=0, pay hook specification
26
26
  Terminal->>Buyback hook: Calls afterPayRecordedWith(...) with specification
27
- Buyback hook->>Uniswap pool: Executes swap
27
+ Buyback hook->>V4 PoolManager: unlock() -> unlockCallback() executes swap
28
28
  Buyback hook->>Controller: Burns swapped tokens, re-mints with reserved rate
29
29
  ```
30
30
 
31
31
  1. A payment is made to a project's terminal.
32
32
  2. The terminal calls `beforePayRecordedWith(context)` on the data hook (this contract).
33
33
  3. The hook calculates how many tokens the payer would get by minting directly (`weight * amount / weightRatio`).
34
- 4. It compares that against a Uniswap V3 quote (user-provided or TWAP-derived with sigmoid slippage tolerance).
34
+ 4. It compares that against a Uniswap V4 quote. The TWAP-based quote uses the pool's oracle hook (if available) or falls back to spot price, then applies sigmoid-based slippage tolerance. The payer/frontend can also supply their own quote in metadata -- the hook uses whichever is higher (more protective).
35
35
  5. If the swap yields more tokens, the hook returns `weight = 0` and specifies itself as a pay hook with the swap amount.
36
36
  6. The terminal calls `afterPayRecordedWith(context)` on the pay hook.
37
- 7. The hook executes the swap, burns the received project tokens, adds any leftover terminal tokens back to the project's balance, and mints the total (swapped + leftover mint) through the controller with `useReservedPercent: true`.
37
+ 7. The hook executes the swap via `POOL_MANAGER.unlock()`, burns the received project tokens, adds any leftover terminal tokens back to the project's balance, and mints the total (swapped + leftover mint) through the controller with `useReservedPercent: true`.
38
38
 
39
- If the swap fails (slippage, insufficient liquidity, etc.), the hook reverts, and the terminal's default minting behavior takes over.
39
+ If the swap fails (slippage, insufficient liquidity, etc.), `_swap` catches the revert and returns 0, which causes `afterPayRecordedWith` to revert with `JBBuybackHook_SpecifiedSlippageExceeded`. The terminal then falls back to its default minting behavior.
40
+
41
+ ## Registry
42
+
43
+ The `JBBuybackHookRegistry` sits between the terminal and individual hook implementations:
44
+
45
+ - **Owner-managed allowlist**: The registry owner calls `allowHook(hook)` / `disallowHook(hook)` to control which implementations projects can use.
46
+ - **Default hook**: The owner calls `setDefaultHook(hook)` to set the fallback for projects that have not explicitly chosen one. Setting a default also adds it to the allowlist.
47
+ - **Per-project override**: Project owners call `setHookFor(projectId, hook)` to select an allowed hook. Permission: `SET_BUYBACK_HOOK` (ID 27).
48
+ - **Locking**: Project owners call `lockHookFor(projectId)` to permanently freeze their hook choice. Once locked, the hook cannot be changed. Same permission: `SET_BUYBACK_HOOK` (ID 27). Locking requires a non-zero hook (either explicitly set or inherited from default). If the project is using the default, locking snapshots that default into the project's storage.
49
+ - **Mint permission delegation**: `hasMintPermissionFor` returns `true` only for the address of the hook active for the project, enabling the hook to mint tokens through the controller.
50
+
51
+ When `disallowHook` removes a hook that is currently the default, the default is also cleared.
40
52
 
41
53
  ## Install
42
54
 
43
55
  For projects using `npm` to manage dependencies (recommended):
44
56
 
45
57
  ```bash
46
- npm install @bananapus/buyback-hook
58
+ npm install @bananapus/buyback-hook-v6
47
59
  ```
48
60
 
49
61
  For projects using `forge` to manage dependencies:
50
62
 
51
63
  ```bash
52
- forge install Bananapus/nana-buyback-hook
64
+ forge install Bananapus/nana-buyback-hook-v6
53
65
  ```
54
66
 
55
- If you're using `forge`, add `@bananapus/buyback-hook/=lib/nana-buyback-hook/` to `remappings.txt`.
67
+ If you're using `forge`, add `@bananapus/buyback-hook-v6/=lib/nana-buyback-hook-v6/` to `remappings.txt`.
56
68
 
57
69
  ## Develop
58
70
 
59
- `nana-buyback-hook` uses [npm](https://www.npmjs.com/) (version >=20.0.0) for package management and [Foundry](https://github.com/foundry-rs/foundry) for builds and tests.
71
+ `nana-buyback-hook-v6` uses [npm](https://www.npmjs.com/) (version >=20.0.0) for package management and [Foundry](https://github.com/foundry-rs/foundry) for builds and tests.
60
72
 
61
73
  ```bash
62
74
  npm ci && forge install
@@ -83,71 +95,113 @@ npm ci && forge install
83
95
 
84
96
  Key `foundry.toml` settings:
85
97
 
86
- - `solc = '0.8.23'`
87
- - `evm_version = 'paris'` (L2-compatible)
98
+ - `solc = '0.8.26'`
99
+ - `evm_version = 'cancun'` (required for Uniswap V4's transient storage `TSTORE`/`TLOAD`)
88
100
  - `optimizer_runs = 100000000`
89
- - `fuzz.runs = 16384`
101
+ - `fuzz.runs = 4096`
90
102
 
91
103
  ## Repository Layout
92
104
 
93
105
  ```
94
- nana-buyback-hook-v5/
106
+ nana-buyback-hook-v6/
95
107
  ├── src/
96
- │ ├── JBBuybackHook.sol # Core buyback hook (data hook + pay hook)
97
- │ ├── JBBuybackHookRegistry.sol # Per-project hook routing
108
+ │ ├── JBBuybackHook.sol # Core buyback hook (data hook + pay hook + V4 unlock callback)
109
+ │ ├── JBBuybackHookRegistry.sol # Per-project hook routing with allowlist and locking
98
110
  │ ├── libraries/
99
- │ │ └── JBSwapLib.sol # Slippage tolerance + price limit calculations
111
+ │ │ └── JBSwapLib.sol # Oracle queries, slippage tolerance, price limit calculations
100
112
  │ └── interfaces/
101
113
  │ ├── IJBBuybackHook.sol # Buyback hook interface
102
114
  │ ├── IJBBuybackHookRegistry.sol # Registry interface
115
+ │ ├── IGeomeanOracle.sol # V4 oracle hook interface (TWAP observation)
103
116
  │ └── external/
104
117
  │ └── IWETH9.sol # WETH wrapper interface
105
118
  ├── script/
106
- │ └── Deploy.s.sol # Deployment script
119
+ │ └── Deploy.s.sol # Multi-chain deployment script (Ethereum, Optimism, Base, Arbitrum)
107
120
  └── test/
108
- ├── Fork.t.sol # Fork tests
109
- └── helpers/ # Test helpers
121
+ ├── V4BuybackHook.t.sol # Core hook unit tests
122
+ ├── Registry.t.sol # Registry unit tests
123
+ ├── JBSwapLib.t.sol # Library unit tests
124
+ ├── MEVScenarios.t.sol # MEV attack scenario tests
125
+ ├── fork/
126
+ │ └── V4ForkTest.t.sol # Mainnet fork integration tests
127
+ └── mock/
128
+ ├── MockOracleHook.sol # Configurable TWAP oracle mock
129
+ ├── MockPoolManager.sol # V4 PoolManager mock
130
+ └── MockSplitHook.sol # Split hook mock
110
131
  ```
111
132
 
112
133
  ## Project Owner Usage Guide
113
134
 
114
135
  ### Setting The Pool
115
136
 
116
- Call `setPoolFor(projectId, fee, twapWindow, terminalToken)` to configure the Uniswap V3 pool for your project. This can only be called once per terminal token.
137
+ Call `setPoolFor(projectId, poolKey, twapWindow, terminalToken)` to configure the Uniswap V4 pool for your project. This can only be called once per terminal token -- pool assignments are immutable once set to prevent swap routing manipulation.
117
138
 
118
- - The `fee` is a `uint24` with the same representation as Uniswap (basis points with 2 decimals): 0.01% = `100`, 0.05% = `500`, 0.3% = `3000`, 1% = `10000`.
119
- - If using ETH, pass `JBConstants.NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) as `terminalToken`.
120
- - The pool address is computed via create2 using Uniswap V3's canonical init code hash.
139
+ - `poolKey` is a Uniswap V4 `PoolKey` struct containing `currency0`, `currency1`, `fee`, `tickSpacing`, and `hooks`. The pool must already be initialized in the V4 PoolManager.
140
+ - The `PoolKey` currencies must match the project token and the terminal token (in either order). The hook validates this on-chain.
141
+ - If using ETH, pass `JBConstants.NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) as `terminalToken`. The hook normalizes this to WETH internally.
142
+ - The project must have already issued an ERC-20 token (via `JBTokens`).
143
+ - Permission: `SET_BUYBACK_POOL` (ID 26).
121
144
 
122
145
  ### Setting TWAP Parameters
123
146
 
124
147
  The TWAP window controls the time period over which the time-weighted average price is computed. A shorter window gives more accurate data but is easier to manipulate; a longer window is more stable but can lag during high volatility.
125
148
 
126
- - Call `setTwapWindowOf(projectId, newWindow)` to change the TWAP window (min: 2 minutes, max: 2 days).
149
+ - Call `setTwapWindowOf(projectId, newWindow)` to change the TWAP window (min: 5 minutes, max: 2 days).
127
150
  - A 30-minute window is a good starting point for high-activity pairs.
151
+ - Permission: `SET_BUYBACK_TWAP` (ID 25).
152
+ - The TWAP window is shared across all pools for the same project.
153
+
154
+ ### Oracle Behavior
155
+
156
+ The hook queries the pool's oracle hook (if any) via the `IGeomeanOracle.observe` interface for TWAP data. If the oracle hook reverts or is not available, the hook falls back to the pool's current spot price and in-range liquidity from the PoolManager.
157
+
158
+ When the oracle returns zero liquidity, the hook returns 0 for the quote, which causes it to fall back to minting rather than swapping -- protecting against swaps in pools with no liquidity.
128
159
 
129
160
  ### Slippage Tolerance
130
161
 
131
162
  The buyback hook uses a continuous sigmoid formula (`JBSwapLib.getSlippageTolerance`) to dynamically calculate slippage tolerance based on the estimated price impact of the swap:
132
163
 
133
- - Small swaps in deep pools get ~2% tolerance.
164
+ - Small swaps in deep pools get ~2% tolerance (minimum floor).
134
165
  - Large swaps relative to pool liquidity approach the 88% ceiling.
135
- - The minimum tolerance is the pool fee + 1% (floor of 2%).
166
+ - The minimum tolerance is the pool fee + 1% buffer (with an absolute floor of 2%).
167
+ - If the calculated slippage tolerance hits the 88% maximum, the hook returns 0 (triggers mint fallback) rather than attempting a swap with extreme slippage.
136
168
 
137
169
  ### Avoiding MEV
138
170
 
139
- Payers/frontends should provide a reasonable minimum quote in metadata to protect against MEV. You can also use the [Flashbots Protect RPC](https://protect.flashbots.net/) for transactions that trigger the buyback hook.
171
+ The hook provides two layers of MEV protection:
172
+
173
+ 1. **sqrtPriceLimitX96**: The V4 swap is executed with a price limit computed from the minimum acceptable output (`JBSwapLib.sqrtPriceLimitFromAmounts`). This stops the swap early if the price moves unfavorably, rather than executing at a bad rate.
174
+ 2. **Quote floor**: The hook always takes the higher of the payer's quote and the TWAP-based quote. A stale or manipulated payer quote cannot produce a worse deal than what the oracle suggests.
175
+
176
+ Payers/frontends should provide a reasonable minimum quote in metadata for additional protection. You can also use the [Flashbots Protect RPC](https://protect.flashbots.net/) for transactions that trigger the buyback hook.
140
177
 
141
178
  ## Payment Metadata
142
179
 
143
- The hook reads metadata with key `"quote"`, encoding `(uint256 amountToSwapWith, uint256 minimumSwapAmountOut)`:
180
+ The hook reads metadata with key `"quote"` (resolved via `JBMetadataResolver`), encoding `(uint256 amountToSwapWith, uint256 minimumSwapAmountOut)`:
144
181
 
145
182
  - If `amountToSwapWith == 0`, the full payment amount is used for the swap.
146
- - If `minimumSwapAmountOut == 0`, a TWAP-based quote is calculated with sigmoid slippage tolerance.
183
+ - If `amountToSwapWith > 0`, only that portion is swapped and the remainder is minted directly.
184
+ - If `amountToSwapWith > totalPaid`, the transaction reverts with `JBBuybackHook_InsufficientPayAmount`.
185
+ - The `minimumSwapAmountOut` is compared against the TWAP-derived minimum -- the higher value is used.
186
+
187
+ ## Supported Chains
188
+
189
+ The deployment script (`Deploy.s.sol`) supports:
190
+
191
+ | Chain | WETH | PoolManager |
192
+ |-------|------|-------------|
193
+ | Ethereum Mainnet | `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` | `0x000000000004444c5dc75cB358380D2e3dE08A90` |
194
+ | Optimism | `0x4200000000000000000000000000000000000006` | `0x9a13f98cb987694c9f086b1f5eb990eea8264ec3` |
195
+ | Base | `0x4200000000000000000000000000000000000006` | `0x498581ff718922c3f8e6a244956af099b2652b2b` |
196
+ | Arbitrum | `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1` | `0x360e68faccca8ca495c1b759fd9eee466db9fb32` |
197
+
198
+ Sepolia testnets for all four chains are also supported.
147
199
 
148
200
  ## Risks
149
201
 
150
- - The hook depends on liquidity in a Uniswap V3 pool. If liquidity migrates to a new pool, the project owner must call `setPoolFor(...)`. If liquidity migrates to a different exchange or Uniswap version, a new hook deployment is needed.
151
- - `setPoolFor` can only be called once per project + terminal token pair. Once set, the pool cannot be changed.
202
+ - The hook depends on liquidity in a Uniswap V4 pool. If liquidity migrates to a new pool with different `PoolKey` parameters (different fee, tickSpacing, or hooks), the hook cannot be redirected -- pool assignments are immutable once set via `setPoolFor`.
203
+ - `setPoolFor` can only be called once per project + terminal token pair. If you need to use a different pool, a new hook deployment is needed.
152
204
  - If the TWAP window isn't set appropriately, payers may receive fewer tokens than expected.
153
205
  - Low liquidity pools are vulnerable to TWAP manipulation by attackers.
206
+ - If the pool's oracle hook is absent or reverts, the hook falls back to spot price, which is more susceptible to manipulation.
207
+ - The registry's `lockHookFor` is irreversible. Once locked, the project cannot change its hook implementation even if a security issue is found in that implementation.
package/SKILLS.md CHANGED
@@ -1,96 +1,195 @@
1
- # nana-buyback-hook-v5
1
+ # Juicebox Buyback Hook
2
2
 
3
3
  ## Purpose
4
4
 
5
- Route project payments through the better of two paths -- minting from the terminal or buying from a Uniswap V3 pool -- to maximize tokens received by the beneficiary while preserving the reserved rate.
5
+ Route project payments through the better of two paths -- minting from the terminal or buying from a Uniswap V4 pool -- to maximize tokens received by the beneficiary while preserving the reserved rate.
6
6
 
7
7
  ## Contracts
8
8
 
9
9
  | Contract | Role |
10
10
  |----------|------|
11
- | `JBBuybackHook` | Core hook: implements `IJBRulesetDataHook` + `IJBPayHook` + `IUniswapV3SwapCallback`. Compares mint vs swap, executes the better route, burns swapped tokens, re-mints through controller with reserved rate. |
12
- | `JBBuybackHookRegistry` | Proxy data hook routing `beforePayRecordedWith` to a per-project or default `JBBuybackHook`. Allows project owners to choose and lock implementations. |
13
- | `JBSwapLib` | Library for computing sigmoid-based slippage tolerance and `sqrtPriceLimitX96` from input/output amounts. |
11
+ | `JBBuybackHook` | Core hook: implements `IJBRulesetDataHook` + `IJBPayHook` + `IUnlockCallback`. Compares mint vs swap via TWAP oracle or spot price, executes the better route through V4 PoolManager, burns swapped tokens, re-mints through controller with reserved rate. |
12
+ | `JBBuybackHookRegistry` | Proxy data hook with allowlist. Routes `beforePayRecordedWith` to a per-project or default `JBBuybackHook`. Project owners choose, and optionally lock, implementations. Registry owner manages the allowlist. |
13
+ | `JBSwapLib` | Library for oracle queries (TWAP or spot), sigmoid-based slippage tolerance, price impact estimation, tick-to-price conversion, and `sqrtPriceLimitX96` computation. |
14
14
 
15
15
  ## Key Functions
16
16
 
17
- | Function | Contract | What it does |
18
- |----------|----------|--------------|
19
- | `beforePayRecordedWith(context)` | `JBBuybackHook` | Data hook: compares mint count vs swap quote. If swap is better, returns `weight=0` and a `JBPayHookSpecification` targeting itself. If mint is better, returns original weight (no hook). |
20
- | `afterPayRecordedWith(context)` | `JBBuybackHook` | Pay hook: pulls tokens from terminal, executes Uniswap V3 swap, burns swapped tokens, returns leftover to project balance, mints total (swapped + leftover mint) via controller with `useReservedPercent: true`. |
21
- | `setPoolFor(projectId, fee, twapWindow, terminalToken)` | `JBBuybackHook` | Set the Uniswap V3 pool for a project+terminal token pair. Computes pool address via create2. Stores pool, TWAP window, and project token. Permission: `SET_BUYBACK_POOL`. |
22
- | `setTwapWindowOf(projectId, newWindow)` | `JBBuybackHook` | Change the TWAP window for a project (2 min to 2 days). Permission: `SET_BUYBACK_TWAP`. |
23
- | `uniswapV3SwapCallback(amount0Delta, amount1Delta, data)` | `JBBuybackHook` | Uniswap V3 callback: validates caller is the expected pool, wraps native tokens if needed, transfers input tokens to the pool. |
24
- | `beforeCashOutRecordedWith(context)` | `JBBuybackHook` | Pass-through: returns cash-out context unchanged (buyback only applies to payments). |
25
- | `hasMintPermissionFor(projectId, ruleset, addr)` | `JBBuybackHook` | Returns `false` (the hook itself does not claim mint permission). |
26
- | `setHookFor(projectId, hook)` | `JBBuybackHookRegistry` | Route a project to a specific allowed buyback hook. Permission: `SET_BUYBACK_POOL`. |
27
- | `lockHookFor(projectId)` | `JBBuybackHookRegistry` | Lock the hook choice for a project (irreversible). |
28
- | `hasMintPermissionFor(projectId, ruleset, addr)` | `JBBuybackHookRegistry` | Returns `true` only if `addr` is the hook registered for the project. |
17
+ ### JBBuybackHook
18
+
19
+ | Function | What it does |
20
+ |----------|--------------|
21
+ | `beforePayRecordedWith(context)` | Data hook (view): compares mint count vs swap quote. Reads `"quote"` metadata for payer-supplied `(amountToSwapWith, minimumSwapAmountOut)`. Computes TWAP-based minimum and uses the higher of the two. If swap yields more tokens, returns `weight=0` and a `JBPayHookSpecification` targeting itself. If mint is better, returns original weight (no hook). |
22
+ | `afterPayRecordedWith(context)` | Pay hook: pulls tokens from terminal, executes V4 swap via `POOL_MANAGER.unlock()`, burns swapped tokens, returns leftover to project balance via `addToBalanceOf`, mints total (swapped + leftover mint) via `controller.mintTokensOf` with `useReservedPercent: true`. Reverts with `JBBuybackHook_SpecifiedSlippageExceeded` if swap output < minimum. |
23
+ | `setPoolFor(projectId, poolKey, twapWindow, terminalToken)` | Set the V4 pool for a project+terminal token pair. Validates: pool is initialized in PoolManager, currencies match project token and terminal token, TWAP window in bounds. Stores pool key, TWAP window, and project token. **Immutable once set.** Permission: `SET_BUYBACK_POOL` (ID 26). |
24
+ | `setTwapWindowOf(projectId, newWindow)` | Change the TWAP window for a project (5 minutes to 2 days). Permission: `SET_BUYBACK_TWAP` (ID 25). |
25
+ | `unlockCallback(data)` | V4 PoolManager callback. Decodes `SwapCallbackData`, computes `sqrtPriceLimitX96`, executes swap, settles input tokens, takes output tokens. Only callable by PoolManager. |
26
+ | `poolKeyOf(projectId, terminalToken)` | Returns the V4 `PoolKey` for a project+terminal token pair. |
27
+ | `beforeCashOutRecordedWith(context)` | Pass-through: returns cash-out context unchanged (buyback only applies to payments). |
28
+ | `hasMintPermissionFor(projectId, ruleset, addr)` | Returns `false` (the hook itself does not claim mint permission -- the registry handles this). |
29
+ | `supportsInterface(interfaceId)` | Returns `true` for `IJBRulesetDataHook`, `IJBPayHook`, `IJBBuybackHook`, `IJBPermissioned`, `IERC165`. |
30
+
31
+ ### JBBuybackHookRegistry
32
+
33
+ | Function | What it does |
34
+ |----------|--------------|
35
+ | `hookOf(projectId)` | Returns the hook for the project, falling back to `defaultHook` if none is set. |
36
+ | `beforePayRecordedWith(context)` | Resolves the project's hook (or default) and delegates the call. |
37
+ | `beforeCashOutRecordedWith(context)` | Pass-through: returns cash-out context unchanged. |
38
+ | `hasMintPermissionFor(projectId, ruleset, addr)` | Returns `true` only if `addr` is the hook registered (or defaulted) for the project. |
39
+ | `setHookFor(projectId, hook)` | Route a project to a specific allowed buyback hook. Reverts if hook is locked or not on the allowlist. Permission: `SET_BUYBACK_HOOK` (ID 27). |
40
+ | `lockHookFor(projectId, expectedHook)` | Lock the hook choice for a project (irreversible). If using default, snapshots it into storage first. Requires a non-zero hook. Reverts with `JBBuybackHookRegistry_HookMismatch` if the resolved hook differs from `expectedHook` (prevents race conditions). Permission: `SET_BUYBACK_HOOK` (ID 27). |
41
+ | `allowHook(hook)` | Add a hook to the allowlist. Owner only. |
42
+ | `disallowHook(hook)` | Remove a hook from the allowlist. If the disallowed hook is the current default, also clears the default. Owner only. |
43
+ | `setDefaultHook(hook)` | Set the default hook (also adds to allowlist). Owner only. |
44
+ | `supportsInterface(interfaceId)` | Returns `true` for `IJBBuybackHookRegistry`, `IJBRulesetDataHook`, `IERC165`. |
29
45
 
30
46
  ## Integration Points
31
47
 
32
48
  | Dependency | Import | Used For |
33
49
  |------------|--------|----------|
34
50
  | `nana-core-v6` | `IJBDirectory`, `IJBController`, `IJBMultiTerminal` | Directory lookups (`isTerminalOf`, `controllerOf`), token minting (`mintTokensOf`), token burning (`burnTokensOf`), balance management (`addToBalanceOf`) |
35
- | `nana-core-v6` | `IJBPrices` | Cross-currency weight ratio when ruleset base currency differs from payment currency |
36
- | `nana-core-v6` | `IJBTokens`, `IJBProjects`, `IJBPermissions` | Token lookups, project ownership, permission checks (`SET_BUYBACK_POOL`, `SET_BUYBACK_TWAP`) |
51
+ | `nana-core-v6` | `IJBPrices` | Cross-currency weight ratio when ruleset base currency differs from payment currency (`pricePerUnitOf`) |
52
+ | `nana-core-v6` | `IJBTokens`, `IJBProjects`, `IJBPermissions` | Token lookups (`tokenOf`), project ownership (`ownerOf`), permission checks |
37
53
  | `nana-core-v6` | `JBMetadataResolver` | Parsing `"quote"` metadata key from payment calldata (contains `amountToSwapWith` and `minimumSwapAmountOut`) |
38
54
  | `nana-core-v6` | `JBRulesetMetadataResolver` | Extracting `baseCurrency()` from packed ruleset metadata |
39
- | `nana-permission-ids-v6` | `JBPermissionIds` | Permission ID constants |
40
- | `@uniswap/v3-core` | `IUniswapV3Pool`, `TickMath` | Pool swaps and tick-to-sqrtPrice conversion |
41
- | `@uniswap/v3-periphery` | `OracleLibrary` | TWAP oracle consultation (`consult`, `getQuoteAtTick`, `getOldestObservationSecondsAgo`) |
55
+ | `nana-permission-ids-v6` | `JBPermissionIds` | Permission ID constants (`SET_BUYBACK_TWAP` = 25, `SET_BUYBACK_POOL` = 26, `SET_BUYBACK_HOOK` = 27) |
56
+ | `@uniswap/v4-core` | `IPoolManager`, `IUnlockCallback`, `PoolKey`, `PoolId`, `Currency`, `BalanceDelta`, `SwapParams`, `TickMath`, `StateLibrary` | V4 pool swaps (`unlock`, `swap`, `settle`, `take`), pool state queries (`getSlot0`, `getLiquidity`), tick math |
42
57
  | `@prb/math` | `mulDiv` | Safe fixed-point multiplication |
43
- | `@openzeppelin/contracts` | `ERC2771Context`, `SafeERC20` | Meta-transactions, safe token transfers |
58
+ | `@openzeppelin/contracts` | `ERC2771Context`, `SafeERC20`, `Ownable` (registry only) | Meta-transactions, safe token transfers, registry ownership |
44
59
 
45
60
  ## Key Types
46
61
 
47
- | Struct/Enum | Key Fields | Used In |
48
- |-------------|------------|---------|
62
+ | Struct/Type | Fields | Used In |
63
+ |-------------|--------|---------|
64
+ | `SwapCallbackData` (internal to `JBBuybackHook`) | `key` (PoolKey), `projectTokenIs0` (bool), `amountIn` (uint256), `minimumSwapAmountOut` (uint256), `terminalToken` (address) | Encoded as `bytes` for `POOL_MANAGER.unlock()`, decoded in `unlockCallback` |
65
+ | `PoolKey` (Uniswap V4) | `currency0` (Currency), `currency1` (Currency), `fee` (uint24), `tickSpacing` (int24), `hooks` (IHooks) | Stored per project+terminalToken in `_poolKeyOf`. Passed to `setPoolFor`. |
49
66
  | `JBBeforePayRecordedContext` | `projectId`, `amount` (token, value, decimals, currency), `weight`, `metadata` | Input to `beforePayRecordedWith` |
50
67
  | `JBAfterPayRecordedContext` | `projectId`, `forwardedAmount`, `weight`, `beneficiary`, `hookMetadata` | Input to `afterPayRecordedWith` |
51
- | `JBPayHookSpecification` | `hook`, `amount`, `metadata` | Returned from `beforePayRecordedWith` when swap is chosen |
68
+ | `JBPayHookSpecification` | `hook` (IJBPayHook), `amount` (uint256), `metadata` (bytes) | Returned from `beforePayRecordedWith` when swap is chosen |
52
69
  | `JBRuleset` | `baseCurrency()` (from packed metadata) | Used for cross-currency weight adjustment |
53
70
 
54
71
  ## JBSwapLib Details
55
72
 
56
73
  | Function | What it does |
57
74
  |----------|--------------|
58
- | `getSlippageTolerance(impact, poolFeeBps)` | Continuous sigmoid: `minSlippage + (maxSlippage - minSlippage) * impact / (impact + K)`. Min = pool fee + 1% (floor 2%), max = 88%. K = 5e16. |
59
- | `calculateImpact(amountIn, liquidity, sqrtP, zeroForOne)` | Estimates price impact scaled by 1e18: `amountIn * 1e18 / liquidity`, normalized by sqrtPrice direction. |
60
- | `sqrtPriceLimitFromAmounts(amountIn, minimumAmountOut, zeroForOne)` | Computes a `sqrtPriceLimitX96` from the minimum acceptable swap rate. Provides MEV protection by stopping the swap if the execution price would be worse than the minimum. |
75
+ | `getQuoteFromOracle(poolManager, key, twapWindow, amountIn, baseToken, quoteToken)` | Queries the pool's oracle hook via `IGeomeanOracle.observe` for TWAP data. If `twapWindow == 0` or the oracle reverts, falls back to spot price from `poolManager.getSlot0`. Returns `(amountOut, arithmeticMeanTick, harmonicMeanLiquidity)`. |
76
+ | `getSlippageTolerance(impact, poolFeeBps)` | Continuous sigmoid: `minSlippage + (maxSlippage - minSlippage) * impact / (impact + K)`. Min = pool fee + 1% (floor 2%), max = 88%. K = 5e16. Returns tolerance in basis points of `SLIPPAGE_DENOMINATOR` (10,000). |
77
+ | `calculateImpact(amountIn, liquidity, sqrtP, zeroForOne)` | Estimates price impact scaled by `IMPACT_PRECISION` (1e18): `amountIn * 1e18 / liquidity`, normalized by sqrtPrice direction. Returns 0 when liquidity or sqrtP is 0. |
78
+ | `getQuoteAtTick(tick, baseAmount, baseToken, quoteToken)` | Converts a tick to a price and returns the equivalent quote amount. Ported from Uniswap V3 `OracleLibrary.getQuoteAtTick` -- pure math, no V3 dependency. |
79
+ | `sqrtPriceLimitFromAmounts(amountIn, minimumAmountOut, zeroForOne)` | Computes a `sqrtPriceLimitX96` from the minimum acceptable swap rate. Provides MEV protection by stopping the swap if the execution price would be worse than the minimum. Handles extreme ratios gracefully with fallback to no limit. |
61
80
 
62
81
  ## Constants
63
82
 
64
83
  | Constant | Value | Purpose |
65
84
  |----------|-------|---------|
66
- | `MIN_TWAP_WINDOW` | `2 minutes` | Minimum TWAP oracle window |
67
- | `MAX_TWAP_WINDOW` | `2 days` | Maximum TWAP oracle window |
68
- | `TWAP_SLIPPAGE_DENOMINATOR` | `10,000` | Basis points denominator |
69
- | `UNCERTAIN_TWAP_SLIPPAGE_TOLERANCE` | `1,050` | 10.5% fallback when impact is zero |
85
+ | `MIN_TWAP_WINDOW` | `5 minutes` (300s) | Minimum TWAP oracle window |
86
+ | `MAX_TWAP_WINDOW` | `2 days` (172,800s) | Maximum TWAP oracle window |
87
+ | `TWAP_SLIPPAGE_DENOMINATOR` | `10,000` | Basis points denominator for slippage |
88
+ | `JBSwapLib.SLIPPAGE_DENOMINATOR` | `10,000` | Basis points denominator (internal to library) |
70
89
  | `JBSwapLib.MAX_SLIPPAGE` | `8,800` | 88% sigmoid ceiling |
71
90
  | `JBSwapLib.IMPACT_PRECISION` | `1e18` | Impact calculation precision |
72
91
  | `JBSwapLib.SIGMOID_K` | `5e16` | Sigmoid curve inflection point |
73
92
 
93
+ ## Permission IDs
94
+
95
+ | ID | Constant | Used By | Grants |
96
+ |----|----------|---------|--------|
97
+ | 25 | `SET_BUYBACK_TWAP` | `JBBuybackHook.setTwapWindowOf` | Change the TWAP window for a project |
98
+ | 26 | `SET_BUYBACK_POOL` | `JBBuybackHook.setPoolFor` | Set the V4 pool for a project+terminal token pair |
99
+ | 27 | `SET_BUYBACK_HOOK` | `JBBuybackHookRegistry.setHookFor`, `lockHookFor` | Choose and lock the hook implementation for a project |
100
+
101
+ ## Events
102
+
103
+ ### JBBuybackHook
104
+
105
+ | Event | Fields |
106
+ |-------|--------|
107
+ | `Swap` | `projectId` (indexed), `amountToSwapWith`, `poolId` (indexed), `amountReceived`, `caller` |
108
+ | `Mint` | `projectId` (indexed), `leftoverAmount`, `tokenCount`, `caller` |
109
+ | `PoolAdded` | `projectId` (indexed), `terminalToken` (indexed), `poolId`, `caller` |
110
+ | `TwapWindowChanged` | `projectId` (indexed), `oldWindow`, `newWindow`, `caller` |
111
+
112
+ ### JBBuybackHookRegistry
113
+
114
+ | Event | Fields |
115
+ |-------|--------|
116
+ | `JBBuybackHookRegistry_AllowHook` | `hook` |
117
+ | `JBBuybackHookRegistry_DisallowHook` | `hook` |
118
+ | `JBBuybackHookRegistry_LockHook` | `projectId` |
119
+ | `JBBuybackHookRegistry_SetDefaultHook` | `hook` |
120
+ | `JBBuybackHookRegistry_SetHook` | `projectId` (indexed), `hook` |
121
+
122
+ ## Custom Errors
123
+
124
+ ### JBBuybackHook
125
+
126
+ | Error | When |
127
+ |-------|------|
128
+ | `JBBuybackHook_CallerNotPoolManager(address caller)` | `unlockCallback` called by someone other than the PoolManager |
129
+ | `JBBuybackHook_InsufficientPayAmount(uint256 swapAmount, uint256 totalPaid)` | Metadata specifies `amountToSwapWith > totalPaid` |
130
+ | `JBBuybackHook_InvalidTwapWindow(uint256 value, uint256 min, uint256 max)` | TWAP window outside [5 minutes, 2 days] |
131
+ | `JBBuybackHook_PoolAlreadySet(PoolId poolId)` | `setPoolFor` called again for same project+token pair |
132
+ | `JBBuybackHook_PoolNotInitialized(PoolId poolId)` | Pool not initialized in V4 PoolManager (sqrtPriceX96 == 0) |
133
+ | `JBBuybackHook_SpecifiedSlippageExceeded(uint256 amount, uint256 minimum)` | Swap output less than minimum acceptable amount |
134
+ | `JBBuybackHook_TerminalTokenIsProjectToken(address, address)` | Terminal token same as project token |
135
+ | `JBBuybackHook_Unauthorized(address caller)` | `afterPayRecordedWith` called by non-terminal |
136
+ | `JBBuybackHook_ZeroProjectToken()` | Project has not issued an ERC-20 token |
137
+ | `JBBuybackHook_ZeroTerminalToken()` | Terminal token resolves to address(0) |
138
+
139
+ ### JBBuybackHookRegistry
140
+
141
+ | Error | When |
142
+ |-------|------|
143
+ | `JBBuybackHookRegistry_HookLocked(uint256 projectId)` | `setHookFor` called on a locked project |
144
+ | `JBBuybackHookRegistry_HookMismatch(IJBRulesetDataHook currentHook, IJBRulesetDataHook expectedHook)` | `lockHookFor` called but resolved hook differs from the caller's `expectedHook` |
145
+ | `JBBuybackHookRegistry_HookNotAllowed(IJBRulesetDataHook hook)` | `setHookFor` called with a hook not on the allowlist |
146
+ | `JBBuybackHookRegistry_HookNotSet(uint256 projectId)` | `lockHookFor` called but no hook is set and no default exists |
147
+ | `JBBuybackHookRegistry_ZeroHook()` | `setDefaultHook` called with `address(0)` |
148
+
74
149
  ## Gotchas
75
150
 
76
- - The hook computes pool addresses via create2 (`POOL_INIT_CODE_HASH`) rather than calling the factory, so `setPoolFor` works even if the pool hasn't been deployed yet.
77
- - Pool addresses are computed using Uniswap V3's canonical init code hash (`0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`). This will not match forks that changed the bytecode.
78
- - `setPoolFor` can only be called once per project+terminalToken pair. After the pool is set, it cannot be changed. Calling again reverts with `JBBuybackHook_PoolAlreadySet`.
79
- - Tokens received from the swap are burned via `controller.burnTokensOf`, then re-minted (along with any leftover-mint count) via `controller.mintTokensOf` with `useReservedPercent: true`. This ensures the reserved rate applies uniformly regardless of the payment route.
80
- - If the swap reverts (slippage, insufficient liquidity, etc.), `_swap` returns 0. The `afterPayRecordedWith` then reverts with `JBBuybackHook_SpecifiedSlippageExceeded(0, minimumSwapAmountOut)`.
81
- - TWAP fallback: when no observations exist (`oldestObservation == 0`), falls back to spot tick and current liquidity rather than reverting.
82
- - The sigmoid slippage formula (`JBSwapLib.getSlippageTolerance`) produces smooth tolerance curves. Small swaps in deep pools get ~2% tolerance; large swaps relative to pool liquidity approach the 88% ceiling.
83
- - `beforePayRecordedWith` is a `view` function -- it cannot modify state. All swap execution happens in `afterPayRecordedWith`.
84
- - The hook validates that `msg.sender` is a registered terminal of the project via `DIRECTORY.isTerminalOf(projectId, msg.sender)`.
85
- - Metadata key `"quote"` encodes `(uint256 amountToSwapWith, uint256 minimumSwapAmountOut)`. If `amountToSwapWith == 0`, the full payment amount is used. If `minimumSwapAmountOut == 0`, a TWAP-based quote is calculated.
86
- - When the payment currency differs from the ruleset's base currency, the hook queries `PRICES.pricePerUnitOf(...)` for the conversion factor.
87
- - State vars are public: `poolOf[projectId][terminalToken]`, `projectTokenOf[projectId]`, `twapWindowOf[projectId]` -- NOT prefixed with underscore.
88
- - `_msgSender()` (ERC-2771) is used instead of `msg.sender` for meta-transaction compatibility in permissioned functions (`setPoolFor`, `setTwapWindowOf`).
89
- - `hasMintPermissionFor` returns `false` on `JBBuybackHook` but returns `addr == address(hook)` on `JBBuybackHookRegistry`. The registry grants mint permission to whichever hook is active for the project.
151
+ - **V4, not V3**: This version uses Uniswap V4 (`IPoolManager`, `PoolKey`, `unlock`/`unlockCallback`). There is no `IUniswapV3Pool`, no `uniswapV3SwapCallback`, no create2 pool address derivation, and no factory. Pools are identified by their `PoolKey` and must be initialized in the V4 PoolManager before calling `setPoolFor`.
152
+ - **Pool immutability**: `setPoolFor` can only be called once per project+terminalToken pair. After the pool key is stored, calling again reverts with `JBBuybackHook_PoolAlreadySet`. This is intentional to prevent swap routing manipulation.
153
+ - **PoolKey validation**: `setPoolFor` validates that the PoolKey's `currency0`/`currency1` match the project token and normalized terminal token. It also checks that the pool is initialized (sqrtPriceX96 != 0).
154
+ - **Burn-and-remint**: Tokens received from the swap are burned via `controller.burnTokensOf`, then re-minted (along with any leftover-mint count) via `controller.mintTokensOf` with `useReservedPercent: true`. This ensures the reserved rate applies uniformly regardless of the payment route.
155
+ - **Swap failure fallback**: If the V4 `POOL_MANAGER.unlock()` call reverts (slippage, insufficient liquidity, etc.), `_swap` catches it with try-catch and returns `(0, true)`. The `swapFailed` flag bypasses the slippage check, allowing the payment to fall through to the mint path. Any WETH wrapped for the swap is unwrapped back to ETH so leftover accounting remains correct.
156
+ - **TWAP oracle fallback**: When the oracle hook is absent or `observe()` reverts, `JBSwapLib.getQuoteFromOracle` falls back to spot price from `poolManager.getSlot0()` and current liquidity from `poolManager.getLiquidity()`.
157
+ - **Zero liquidity protection**: If the oracle/spot returns zero liquidity (`harmonicMeanLiquidity == 0`), `_getQuote` returns 0, which ensures the hook falls back to minting rather than attempting a swap in an empty pool.
158
+ - **Sigmoid slippage ceiling**: If `getSlippageTolerance` returns `>= TWAP_SLIPPAGE_DENOMINATOR` (10,000 bps = 100%), `_getQuote` returns 0 to trigger mint fallback.
159
+ - **Quote floor**: `beforePayRecordedWith` uses the **higher** of the payer's supplied `minimumSwapAmountOut` and the TWAP-derived minimum. A stale or manipulated payer quote cannot produce a worse deal than the oracle suggests.
160
+ - **sqrtPriceLimitX96 protection**: The `unlockCallback` computes a `sqrtPriceLimitX96` from `amountIn` and `minimumSwapAmountOut`. The V4 swap stops early if the price moves beyond this limit, providing on-chain MEV protection.
161
+ - **`beforePayRecordedWith` is a `view` function**: It cannot modify state. All swap execution happens in `afterPayRecordedWith`.
162
+ - **Terminal validation**: `afterPayRecordedWith` validates that `msg.sender` is a registered terminal of the project via `DIRECTORY.isTerminalOf(projectId, IJBTerminal(msg.sender))`.
163
+ - **Native token handling**: When the terminal token is `JBConstants.NATIVE_TOKEN`, the hook normalizes to WETH for storage and pool lookups. For V4 settlement, if the pool's input currency is `address(0)` (native ETH), it uses `settle{value:}`; otherwise it wraps to WETH first via `WETH.deposit{value:}`.
164
+ - **Metadata key**: `"quote"` encodes `(uint256 amountToSwapWith, uint256 minimumSwapAmountOut)`. If `amountToSwapWith == 0`, the full payment amount is used. If not provided at all, same behavior.
165
+ - **State variable names**: Public: `projectTokenOf[projectId]`, `twapWindowOf[projectId]`. Internal with public getter: `poolKeyOf(projectId, terminalToken)` (backed by `_poolKeyOf`). Internal only: `_poolIsSet[projectId][terminalToken]`.
166
+ - **ERC-2771**: `_msgSender()` (ERC-2771) is used instead of `msg.sender` for meta-transaction compatibility in permissioned functions (`setPoolFor`, `setTwapWindowOf`).
167
+ - **Mint permission**: `hasMintPermissionFor` returns `false` on `JBBuybackHook` but returns `addr == address(hook)` on `JBBuybackHookRegistry`. The registry grants mint permission to whichever hook is active for the project.
168
+ - **Registry locking**: `lockHookFor(projectId, expectedHook)` snapshots the default into `_hookOf[projectId]` if the project hasn't explicitly set one. The `expectedHook` parameter prevents race conditions: if the resolved hook differs from what the caller expects, it reverts with `HookMismatch`. This prevents a later `setDefaultHook` from changing the locked project's hook.
169
+ - **Registry setDefaultHook**: `setDefaultHook(address(0))` reverts with `ZeroHook` to prevent DoS when projects without a specific hook try to use the default.
170
+ - **Registry disallowHook**: If `disallowHook` removes the current default, it also clears `defaultHook` to `address(0)`.
171
+ - **Currency conversion**: When the payment currency differs from the ruleset's base currency, the hook queries `PRICES.pricePerUnitOf(...)` for the conversion factor. This is used both in `beforePayRecordedWith` (for comparing mint vs swap) and in `afterPayRecordedWith` (for computing leftover mint tokens).
90
172
 
91
173
  ## Example Integration
92
174
 
93
175
  ```solidity
176
+ import {JBBuybackHook} from "@bananapus/buyback-hook-v6/src/JBBuybackHook.sol";
177
+ import {JBBuybackHookRegistry} from "@bananapus/buyback-hook-v6/src/JBBuybackHookRegistry.sol";
178
+ import {IWETH9} from "@bananapus/buyback-hook-v6/src/interfaces/external/IWETH9.sol";
179
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
180
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
181
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
182
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
183
+ import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
184
+
185
+ // Deploy the registry (owner-managed)
186
+ JBBuybackHookRegistry registry = new JBBuybackHookRegistry(
187
+ permissions,
188
+ projects,
189
+ registryOwner, // address that manages the hook allowlist
190
+ trustedForwarder
191
+ );
192
+
94
193
  // Deploy the buyback hook
95
194
  JBBuybackHook hook = new JBBuybackHook(
96
195
  directory,
@@ -98,23 +197,35 @@ JBBuybackHook hook = new JBBuybackHook(
98
197
  prices,
99
198
  projects,
100
199
  tokens,
101
- weth,
102
- uniswapV3Factory, // factory address for create2 pool derivation
200
+ IWETH9(weth),
201
+ IPoolManager(poolManager),
103
202
  trustedForwarder
104
203
  );
105
204
 
106
- // Configure a pool for project 1 with a 0.3% fee tier and 30-minute TWAP
107
- IUniswapV3Pool pool = hook.setPoolFor({
205
+ // Register the hook as the default
206
+ registry.setDefaultHook(hook);
207
+
208
+ // Configure a V4 pool for project 1 with a 30-minute TWAP
209
+ // The pool must already be initialized in the V4 PoolManager.
210
+ address projectToken = address(tokens.tokenOf(1));
211
+
212
+ hook.setPoolFor({
108
213
  projectId: 1,
109
- fee: 3000, // 0.3%
214
+ poolKey: PoolKey({
215
+ currency0: Currency.wrap(projectToken < weth ? projectToken : weth),
216
+ currency1: Currency.wrap(projectToken < weth ? weth : projectToken),
217
+ fee: 3000, // 0.3% in V4 fee units (hundredths of a bip)
218
+ tickSpacing: 60,
219
+ hooks: IHooks(address(0)) // or an oracle hook address
220
+ }),
110
221
  twapWindow: 30 minutes,
111
- terminalToken: address(weth) // or JBConstants.NATIVE_TOKEN
222
+ terminalToken: JBConstants.NATIVE_TOKEN // or address(weth)
112
223
  });
113
224
 
114
- // Set the hook as the project's data hook in the ruleset config:
225
+ // Set the registry as the project's data hook in the ruleset config:
115
226
  // rulesetConfig.metadata.useDataHookForPay = true;
116
- // rulesetConfig.metadata.dataHook = address(hook);
227
+ // rulesetConfig.metadata.dataHook = address(registry);
117
228
 
118
- // Now when someone pays project 1, the hook automatically
119
- // compares mint vs swap and takes the better route.
229
+ // Now when someone pays project 1, the registry delegates to the hook,
230
+ // which compares mint vs swap and takes the better route.
120
231
  ```
package/foundry.lock ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "lib/forge-std": {
3
+ "rev": "2b59872eee0b8088ddcade39fe8c041e17bb79c0"
4
+ },
5
+ "lib/sphinx": {
6
+ "branch": {
7
+ "name": "v0.23.0",
8
+ "rev": "5fb24a825f46bd6ae0b5359fe0da1d2346126b09"
9
+ }
10
+ }
11
+ }
package/foundry.toml CHANGED
@@ -1,7 +1,8 @@
1
1
  [profile.default]
2
2
  solc = '0.8.26'
3
- evm_version = 'paris'
4
- optimizer_runs = 100000000
3
+ evm_version = 'cancun'
4
+ via_ir = true
5
+ optimizer_runs = 200
5
6
  libs = ["node_modules", "lib"]
6
7
  fs_permissions = [{ access = "read-write", path = "./"}]
7
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/buyback-hook-v6",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,14 +18,12 @@
18
18
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-buyback-hook-v6'"
19
19
  },
20
20
  "dependencies": {
21
- "@bananapus/core-v6": "^0.0.5",
22
- "@bananapus/permission-ids-v6": "^0.0.3",
23
- "@exhausted-pigeon/uniswap-v3-forge-quoter": "^1.0.2",
21
+ "@bananapus/core-v6": "^0.0.9",
22
+ "@bananapus/permission-ids-v6": "^0.0.4",
24
23
  "@openzeppelin/contracts": "^5.2.0",
25
- "@uniswap/v3-core": "github:Uniswap/v3-core#0.8",
26
- "@uniswap/v3-periphery": "github:Uniswap/v3-periphery#0.8"
24
+ "@uniswap/v4-core": "^1.0.2"
27
25
  },
28
26
  "devDependencies": {
29
- "@sphinx-labs/plugins": "^0.33.1"
27
+ "@sphinx-labs/plugins": "^0.33.2"
30
28
  }
31
29
  }