@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 +91 -37
- package/SKILLS.md +168 -57
- package/foundry.lock +11 -0
- package/foundry.toml +3 -2
- package/package.json +5 -7
- package/script/Deploy.s.sol +15 -15
- package/src/JBBuybackHook.sol +355 -363
- package/src/JBBuybackHookRegistry.sol +57 -46
- package/src/interfaces/IGeomeanOracle.sol +22 -0
- package/src/interfaces/IJBBuybackHook.sol +63 -39
- package/src/interfaces/IJBBuybackHookRegistry.sol +23 -5
- package/src/libraries/JBSwapLib.sol +181 -27
- package/test/JBSwapLib.t.sol +22 -22
- package/test/MEVScenarios.t.sol +417 -0
- package/test/Registry.t.sol +24 -9
- package/test/V4BuybackHook.t.sol +1059 -0
- package/test/fork/V4ForkTest.t.sol +1093 -0
- package/test/mock/MockOracleHook.sol +59 -0
- package/test/mock/MockPoolManager.sol +149 -0
- package/test/regression/L44_BalanceDeltaLeftover.t.sol +470 -0
- package/test/regression/L45_OldWindowEvent.t.sol +204 -0
- package/test/regression/L46_DefaultHookZeroCheck.t.sol +42 -0
- package/test/regression/LockRace_ExpectedHook.t.sol +132 -0
- package/test/regression/M34_SwapFailureMintFallback.t.sol +303 -0
- package/test/AMMIntegration.t.sol +0 -467
- package/test/BuybackHookAttacks.t.sol +0 -294
- package/test/Fork.t.sol +0 -855
- package/test/MEVProtection.t.sol +0 -407
- package/test/OrderSizeStress.t.sol +0 -474
- package/test/SigmoidValidation.t.sol +0 -399
- package/test/Unit.t.sol +0 -2199
- package/test/helpers/PoolAddress.sol +0 -49
- package/test/helpers/TestBaseWorkflowV3.sol +0 -259
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
#
|
|
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
|
|
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)
|
|
12
|
-
| `JBBuybackHookRegistry` | A proxy data hook that delegates `beforePayRecordedWith` to a per-project or default `JBBuybackHook` instance.
|
|
13
|
-
| `JBSwapLib` | Shared library for slippage tolerance and
|
|
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
|
|
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->>
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
87
|
-
- `evm_version = '
|
|
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 =
|
|
101
|
+
- `fuzz.runs = 4096`
|
|
90
102
|
|
|
91
103
|
## Repository Layout
|
|
92
104
|
|
|
93
105
|
```
|
|
94
|
-
nana-buyback-hook-
|
|
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 #
|
|
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 #
|
|
119
|
+
│ └── Deploy.s.sol # Multi-chain deployment script (Ethereum, Optimism, Base, Arbitrum)
|
|
107
120
|
└── test/
|
|
108
|
-
├──
|
|
109
|
-
|
|
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,
|
|
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
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
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:
|
|
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
|
-
|
|
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"
|
|
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 `
|
|
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
|
|
151
|
-
- `setPoolFor` can only be called once per project + terminal token pair.
|
|
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
|
-
#
|
|
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
|
|
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` + `
|
|
12
|
-
| `JBBuybackHookRegistry` | Proxy data hook
|
|
13
|
-
| `JBSwapLib` | Library for
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
|
20
|
-
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `hasMintPermissionFor(projectId, ruleset, addr)` |
|
|
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
|
|
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/
|
|
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/
|
|
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
|
|
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
|
-
| `
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
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` | `
|
|
67
|
-
| `MAX_TWAP_WINDOW` | `2 days` | Maximum TWAP oracle window |
|
|
68
|
-
| `TWAP_SLIPPAGE_DENOMINATOR` | `10,000` | Basis points denominator |
|
|
69
|
-
| `
|
|
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
|
-
-
|
|
77
|
-
- Pool
|
|
78
|
-
- `setPoolFor`
|
|
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
|
|
81
|
-
- TWAP fallback
|
|
82
|
-
-
|
|
83
|
-
- `
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
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
|
-
|
|
200
|
+
IWETH9(weth),
|
|
201
|
+
IPoolManager(poolManager),
|
|
103
202
|
trustedForwarder
|
|
104
203
|
);
|
|
105
204
|
|
|
106
|
-
//
|
|
107
|
-
|
|
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
|
-
|
|
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)
|
|
222
|
+
terminalToken: JBConstants.NATIVE_TOKEN // or address(weth)
|
|
112
223
|
});
|
|
113
224
|
|
|
114
|
-
// Set the
|
|
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(
|
|
227
|
+
// rulesetConfig.metadata.dataHook = address(registry);
|
|
117
228
|
|
|
118
|
-
// Now when someone pays project 1, the hook
|
|
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
package/foundry.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/buyback-hook-v6",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
22
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
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/
|
|
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.
|
|
27
|
+
"@sphinx-labs/plugins": "^0.33.2"
|
|
30
28
|
}
|
|
31
29
|
}
|