@bananapus/router-terminal-v6 0.0.1
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/LICENSE +21 -0
- package/README.md +145 -0
- package/SKILLS.md +101 -0
- package/foundry.toml +22 -0
- package/package.json +30 -0
- package/script/Deploy.s.sol +130 -0
- package/script/helpers/RouterTerminalDeploymentLib.sol +76 -0
- package/slither-ci.config.json +10 -0
- package/src/JBRouterTerminal.sol +1355 -0
- package/src/JBRouterTerminalRegistry.sol +466 -0
- package/src/interfaces/IJBRouterTerminal.sol +45 -0
- package/src/interfaces/IJBRouterTerminalRegistry.sol +62 -0
- package/src/interfaces/IWETH9.sol +13 -0
- package/src/libraries/JBSwapLib.sol +159 -0
- package/src/structs/PoolInfo.sol +14 -0
- package/test/RouterTerminal.t.sol +865 -0
- package/test/RouterTerminalRegistry.t.sol +333 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Bananapus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# nana-router-terminal-v6
|
|
2
|
+
|
|
3
|
+
A Juicebox terminal that accepts payments in any token, dynamically discovers what token each destination project accepts, and routes the payment there — via direct forwarding, Uniswap swap, JB token cashout, or a combination. Supports both Uniswap V3 and V4 pools, choosing whichever offers better liquidity.
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
| Contract | Description |
|
|
10
|
+
|----------|-------------|
|
|
11
|
+
| `JBRouterTerminal` | Core terminal. Accepts any token via `pay` or `addToBalanceOf`, discovers the destination project's accepted token, and routes there — swapping through Uniswap V3 or V4 pools if needed. Uses TWAP oracle for automatic slippage protection when the caller doesn't provide a quote. |
|
|
12
|
+
| `JBRouterTerminalRegistry` | A proxy terminal that delegates `pay` and `addToBalanceOf` to a per-project or default `JBRouterTerminal` instance. Allows project owners to choose (and lock) which router terminal implementation they use. |
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
1. A payer calls `pay(projectId, token, amount, ...)` with any token.
|
|
17
|
+
2. The terminal accepts the token (supports ERC-20 approvals and Permit2).
|
|
18
|
+
3. It discovers the destination project's accepted token by querying the directory.
|
|
19
|
+
4. If the input token differs from the accepted token, it converts it — by cashing out JB project tokens, swapping through the best Uniswap V3 or V4 pool across multiple fee tiers, or both.
|
|
20
|
+
5. Slippage protection: the caller can pass a minimum output quote in metadata (`quoteForSwap` key), or the terminal calculates one from the pool's TWAP oracle with dynamic slippage tolerance.
|
|
21
|
+
6. The output tokens are forwarded to the project's primary terminal via `terminal.pay(...)` or `terminal.addToBalanceOf(...)`.
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
sequenceDiagram
|
|
25
|
+
participant Frontend client
|
|
26
|
+
participant JBDirectory
|
|
27
|
+
participant JBRouterTerminal
|
|
28
|
+
participant Uniswap V3/V4
|
|
29
|
+
participant JBMultiTerminal
|
|
30
|
+
Note left of Frontend client: User pays project with USDC
|
|
31
|
+
Frontend client->>JBDirectory: Checks project's primary USDC terminal
|
|
32
|
+
JBDirectory->>Frontend client: Returns JBRouterTerminal
|
|
33
|
+
Frontend client->>JBRouterTerminal: Calls pay(...) with USDC (and optional quote)
|
|
34
|
+
JBRouterTerminal->>JBDirectory: Discovers project's accepted token (ETH)
|
|
35
|
+
JBRouterTerminal->>Uniswap V3/V4: Finds best pool, swaps USDC for ETH
|
|
36
|
+
JBRouterTerminal->>JBMultiTerminal: Pays ETH to project's terminal
|
|
37
|
+
Note right of JBMultiTerminal: Mint tokens for original beneficiary
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Routing Strategies
|
|
41
|
+
|
|
42
|
+
The terminal can route payments via multiple strategies:
|
|
43
|
+
|
|
44
|
+
- **Direct forwarding** — If the input token is already accepted by the destination terminal.
|
|
45
|
+
- **Uniswap V3 swap** — Through the highest-liquidity V3 pool across fee tiers (0.01%, 0.05%, 0.3%, 1%).
|
|
46
|
+
- **Uniswap V4 swap** — Through V4 pools, also searching multiple fee/tick-spacing configurations.
|
|
47
|
+
- **JB token cashout** — Redeeming JB project tokens to get the needed output token.
|
|
48
|
+
- **Combination** — Chaining strategies when no single route works.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
For projects using `npm` to manage dependencies (recommended):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install @bananapus/router-terminal-v6
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For projects using `forge` to manage dependencies:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
forge install Bananapus/nana-router-terminal-v6
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If you're using `forge`, add `@bananapus/router-terminal-v6/=lib/nana-router-terminal-v6/` to `remappings.txt`.
|
|
65
|
+
|
|
66
|
+
## Develop
|
|
67
|
+
|
|
68
|
+
`nana-router-terminal-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.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm ci && forge install
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
| Command | Description |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| `forge build` | Compile the contracts and write artifacts to `out`. |
|
|
77
|
+
| `forge test` | Run the tests. |
|
|
78
|
+
| `forge fmt` | Lint. |
|
|
79
|
+
| `forge build --sizes` | Get contract sizes. |
|
|
80
|
+
| `forge coverage` | Generate a test coverage report. |
|
|
81
|
+
| `forge clean` | Remove the build artifacts and cache directories. |
|
|
82
|
+
|
|
83
|
+
### Scripts
|
|
84
|
+
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| `npm test` | Run local tests. |
|
|
88
|
+
| `npm run coverage` | Generate an LCOV test coverage report. |
|
|
89
|
+
|
|
90
|
+
### Configuration
|
|
91
|
+
|
|
92
|
+
Key `foundry.toml` settings:
|
|
93
|
+
|
|
94
|
+
- `solc = '0.8.26'`
|
|
95
|
+
- `evm_version = 'cancun'` (required for Uniswap V4's transient storage)
|
|
96
|
+
- `optimizer_runs = 100000000`
|
|
97
|
+
|
|
98
|
+
## Repository Layout
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
nana-router-terminal-v6/
|
|
102
|
+
├── src/
|
|
103
|
+
│ ├── JBRouterTerminal.sol # Core router terminal
|
|
104
|
+
│ ├── JBRouterTerminalRegistry.sol # Per-project terminal routing
|
|
105
|
+
│ ├── interfaces/
|
|
106
|
+
│ │ ├── IJBRouterTerminal.sol # Router terminal interface
|
|
107
|
+
│ │ ├── IJBRouterTerminalRegistry.sol # Registry interface
|
|
108
|
+
│ │ └── IWETH9.sol # WETH wrapper interface
|
|
109
|
+
│ ├── libraries/
|
|
110
|
+
│ │ └── JBSwapLib.sol # Swap math and pool discovery
|
|
111
|
+
│ └── structs/
|
|
112
|
+
│ └── PoolInfo.sol # Pool metadata struct
|
|
113
|
+
├── script/
|
|
114
|
+
│ ├── Deploy.s.sol # Deployment script
|
|
115
|
+
│ └── helpers/
|
|
116
|
+
│ └── RouterTerminalDeploymentLib.sol # Deployment address loader
|
|
117
|
+
└── test/
|
|
118
|
+
├── RouterTerminal.t.sol # Terminal tests
|
|
119
|
+
└── RouterTerminalRegistry.t.sol # Registry tests
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Payment Metadata
|
|
123
|
+
|
|
124
|
+
The `JBRouterTerminal` accepts encoded `metadata` in its `pay(...)` function. Metadata is decoded using `JBMetadataResolver`:
|
|
125
|
+
|
|
126
|
+
```solidity
|
|
127
|
+
(bool exists, bytes memory quote) =
|
|
128
|
+
JBMetadataResolver.getDataFor(JBMetadataResolver.getId("quoteForSwap"), metadata);
|
|
129
|
+
|
|
130
|
+
if (exists) {
|
|
131
|
+
(minAmountOut) = abi.decode(quote, (uint256));
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
If no quote is provided, the terminal calculates one from the pool's TWAP oracle with a dynamic slippage tolerance based on the estimated price impact of the swap.
|
|
136
|
+
|
|
137
|
+
The terminal also supports Permit2 metadata (key: `"permit2"`) for gasless token approvals.
|
|
138
|
+
|
|
139
|
+
## Risks
|
|
140
|
+
|
|
141
|
+
- The terminal never holds a token balance. After every swap, all output tokens are forwarded and leftover input tokens are returned to the payer.
|
|
142
|
+
- Pool discovery is dynamic — the terminal searches V3 and V4 pools at runtime. If pool liquidity changes between discovery and execution, slippage protection prevents losses.
|
|
143
|
+
- TWAP fallback: when no TWAP observations exist, the terminal falls back to the pool's current spot tick rather than reverting.
|
|
144
|
+
- The `receive()` function only accepts ETH from the WETH contract (during unwrap). All other senders revert.
|
|
145
|
+
- Uniswap V4 requires `cancun` EVM version (transient storage opcodes). This terminal will not work on chains without EIP-1153 support.
|
package/SKILLS.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# nana-router-terminal-v6
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Accept payments in any ERC-20 token (or native ETH), dynamically discover what token the destination project accepts, and route there — via Uniswap V3/V4 swap, direct forwarding, JB token cashout, or a combination.
|
|
6
|
+
|
|
7
|
+
## Contracts
|
|
8
|
+
|
|
9
|
+
| Contract | Role |
|
|
10
|
+
|----------|------|
|
|
11
|
+
| `JBRouterTerminal` | Core terminal: accepts any token, discovers the best route to the destination project's accepted token, swaps via Uniswap V3 or V4, forwards to primary terminal. Implements `IJBTerminal`, `IJBPermitTerminal`, `IUniswapV3SwapCallback`, `IUnlockCallback`. |
|
|
12
|
+
| `JBRouterTerminalRegistry` | Proxy terminal routing `pay`/`addToBalanceOf` to a per-project or default `JBRouterTerminal`. Implements `IJBTerminal`. |
|
|
13
|
+
|
|
14
|
+
## Key Functions
|
|
15
|
+
|
|
16
|
+
| Function | Contract | What it does |
|
|
17
|
+
|----------|----------|--------------|
|
|
18
|
+
| `pay(projectId, token, amount, beneficiary, minReturnedTokens, memo, metadata)` | `JBRouterTerminal` | Accept any token, discover destination's accepted token, swap if needed via best V3/V4 pool, forward to project's primary terminal. Returns project token count. |
|
|
19
|
+
| `addToBalanceOf(projectId, token, amount, shouldReturnHeldFees, memo, metadata)` | `JBRouterTerminal` | Same routing flow but calls `terminal.addToBalanceOf(...)` instead of `terminal.pay(...)`. |
|
|
20
|
+
| `discoverPool(normalizedTokenIn, normalizedTokenOut)` | `JBRouterTerminal` | Search V3 factory for highest liquidity pool across 4 fee tiers (0.01%, 0.05%, 0.3%, 1%). |
|
|
21
|
+
| `discoverBestPool(normalizedTokenIn, normalizedTokenOut)` | `JBRouterTerminal` | Discover best pool across both V3 and V4, comparing liquidity. Returns `PoolInfo` with version, key, and liquidity. |
|
|
22
|
+
| `setTerminalFor(projectId, terminal)` | `JBRouterTerminalRegistry` | Route a project to a specific allowed router terminal. Requires `SET_ROUTER_TERMINAL` permission. |
|
|
23
|
+
| `lockTerminalFor(projectId)` | `JBRouterTerminalRegistry` | Lock the terminal choice for a project (irreversible). |
|
|
24
|
+
| `allowTerminal(terminal)` | `JBRouterTerminalRegistry` | Owner-only: add a terminal to the allowlist. |
|
|
25
|
+
| `setDefaultTerminal(terminal)` | `JBRouterTerminalRegistry` | Owner-only: set the default terminal for projects without a custom choice. |
|
|
26
|
+
|
|
27
|
+
## Integration Points
|
|
28
|
+
|
|
29
|
+
| Dependency | Import | Used For |
|
|
30
|
+
|------------|--------|----------|
|
|
31
|
+
| `nana-core-v6` | `IJBDirectory`, `IJBTerminal`, `IJBProjects`, `IJBPermissions`, `IJBTokens` | Directory lookups (`primaryTerminalOf`), project ownership, permission checks, token discovery |
|
|
32
|
+
| `nana-core-v6` | `JBMetadataResolver` | Parsing `quoteForSwap` and `permit2` metadata from calldata |
|
|
33
|
+
| `nana-core-v6` | `JBAccountingContext`, `JBSingleAllowance` | Token accounting and Permit2 allowance structs |
|
|
34
|
+
| `nana-permission-ids-v6` | `JBPermissionIds` | Permission ID constants (`SET_ROUTER_TERMINAL`) |
|
|
35
|
+
| `@uniswap/v3-core` | `IUniswapV3Pool`, `IUniswapV3Factory`, `TickMath` | V3 pool swaps, factory pool discovery, tick math |
|
|
36
|
+
| `@uniswap/v3-periphery` | `OracleLibrary` | TWAP oracle consultation (`consult`, `getQuoteAtTick`, `getOldestObservationSecondsAgo`) |
|
|
37
|
+
| `@uniswap/v4-core` | `IPoolManager`, `PoolKey`, `Currency`, `StateLibrary` | V4 pool swaps and liquidity queries |
|
|
38
|
+
| `@uniswap/permit2` | `IPermit2`, `IAllowanceTransfer` | Gasless token approvals |
|
|
39
|
+
| `@openzeppelin/contracts` | `Ownable`, `ERC2771Context`, `SafeERC20` | Access control, meta-transactions, safe transfers |
|
|
40
|
+
|
|
41
|
+
## Key Types
|
|
42
|
+
|
|
43
|
+
| Struct/Enum | Key Fields | Used In |
|
|
44
|
+
|-------------|------------|---------|
|
|
45
|
+
| `PoolInfo` | `isV4`, `v3Pool`, `v4Key`, `liquidity` | Returned by `discoverBestPool`. Indicates whether the best route is V3 or V4 and stores the pool details. |
|
|
46
|
+
| `JBAccountingContext` | `token`, `decimals`, `currency` | Token accounting contexts for accepted tokens. |
|
|
47
|
+
| `JBSingleAllowance` | `sigDeadline`, `amount`, `expiration`, `nonce`, `signature` | Decoded from `permit2` metadata key for gasless approvals. |
|
|
48
|
+
|
|
49
|
+
## Constants
|
|
50
|
+
|
|
51
|
+
| Constant | Value | Purpose |
|
|
52
|
+
|----------|-------|---------|
|
|
53
|
+
| `DEFAULT_TWAP_WINDOW` | `10 minutes` | Default TWAP oracle window for auto-discovered pools |
|
|
54
|
+
| `FEE_TIERS` | `[3000, 500, 10000, 100]` | V3 fee tiers to search (0.3%, 0.05%, 1%, 0.01%) |
|
|
55
|
+
| `V4_FEES` | `[3000, 500, 10000, 100]` | V4 fee tiers to search |
|
|
56
|
+
| `V4_TICK_SPACINGS` | `[60, 10, 200, 1]` | V4 tick spacings paired with fee tiers |
|
|
57
|
+
| `SLIPPAGE_DENOMINATOR` | `10,000` | Basis points denominator for slippage |
|
|
58
|
+
|
|
59
|
+
## Gotchas
|
|
60
|
+
|
|
61
|
+
- The terminal never holds a token balance. After every swap, all output tokens are forwarded and leftover input tokens are returned to the payer.
|
|
62
|
+
- Unlike the swap terminal which had a fixed `TOKEN_OUT`, the router terminal dynamically discovers what token each project accepts. This makes it a universal entry point.
|
|
63
|
+
- Pool discovery runs at call time — it searches V3 and V4 pools across multiple fee tiers. The best pool (by liquidity) wins. This is gas-intensive but ensures optimal routing.
|
|
64
|
+
- When `tokenIn == NATIVE_TOKEN`, the terminal wraps ETH to WETH before swapping. When the output is `NATIVE_TOKEN`, it unwraps WETH after swapping.
|
|
65
|
+
- The `receive()` function only accepts ETH from the WETH contract (during unwrap). All other senders revert.
|
|
66
|
+
- TWAP fallback: when no observations exist (`oldestObservation == 0`), the terminal falls back to the pool's current spot tick and liquidity rather than reverting.
|
|
67
|
+
- Uniswap V4 requires `cancun` EVM version (transient storage). Chains without EIP-1153 cannot use V4 routing — the terminal falls back to V3.
|
|
68
|
+
- The `JBRouterTerminalRegistry` handles token custody during delegation — it transfers tokens from the payer to itself, then to the underlying terminal.
|
|
69
|
+
- Metadata keys: `"quoteForSwap"` for the minimum output amount, `"permit2"` for gasless approvals.
|
|
70
|
+
- `_msgSender()` (ERC-2771) is used instead of `msg.sender` for meta-transaction compatibility.
|
|
71
|
+
- The `JBSwapLib` library contains the core swap execution logic, extracted for contract size management.
|
|
72
|
+
|
|
73
|
+
## Example Integration
|
|
74
|
+
|
|
75
|
+
```solidity
|
|
76
|
+
import {JBRouterTerminal} from "@bananapus/router-terminal-v6/src/JBRouterTerminal.sol";
|
|
77
|
+
import {JBRouterTerminalRegistry} from "@bananapus/router-terminal-v6/src/JBRouterTerminalRegistry.sol";
|
|
78
|
+
|
|
79
|
+
// The registry is the entry point for payments.
|
|
80
|
+
// It delegates to per-project or default JBRouterTerminal instances.
|
|
81
|
+
|
|
82
|
+
// Pay project 1 with USDC — the router terminal discovers that
|
|
83
|
+
// project 1 accepts ETH, finds the best USDC/WETH pool across
|
|
84
|
+
// V3 and V4, swaps, and forwards ETH to the project's terminal.
|
|
85
|
+
IERC20(usdc).approve(address(registry), 1000e6);
|
|
86
|
+
registry.pay{value: 0}(
|
|
87
|
+
1, // projectId
|
|
88
|
+
usdc, // token (USDC)
|
|
89
|
+
1000e6, // amount (1000 USDC)
|
|
90
|
+
beneficiary, // who receives project tokens
|
|
91
|
+
0, // minReturnedTokens
|
|
92
|
+
"Payment via router",
|
|
93
|
+
"" // metadata (empty = use TWAP quote)
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Project owners can choose a specific router terminal:
|
|
97
|
+
registry.setTerminalFor(projectId, preferredTerminal);
|
|
98
|
+
|
|
99
|
+
// And lock it permanently:
|
|
100
|
+
registry.lockTerminalFor(projectId);
|
|
101
|
+
```
|
package/foundry.toml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[profile.default]
|
|
2
|
+
solc = '0.8.26'
|
|
3
|
+
evm_version = 'cancun'
|
|
4
|
+
optimizer_runs = 100000000
|
|
5
|
+
libs = ["node_modules", "lib"]
|
|
6
|
+
fs_permissions = [{ access = "read-write", path = "./"}]
|
|
7
|
+
|
|
8
|
+
[profile.ci_sizes]
|
|
9
|
+
optimizer_runs = 200
|
|
10
|
+
|
|
11
|
+
[fuzz]
|
|
12
|
+
runs = 4096
|
|
13
|
+
|
|
14
|
+
[invariant]
|
|
15
|
+
runs = 1024
|
|
16
|
+
depth = 100
|
|
17
|
+
fail_on_revert = false
|
|
18
|
+
|
|
19
|
+
[fmt]
|
|
20
|
+
number_underscore = "thousands"
|
|
21
|
+
multiline_func_header = "all"
|
|
22
|
+
wrap_comments = true
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bananapus/router-terminal-v6",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Bananapus/nana-router-terminal-v6"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.0.0"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "forge test",
|
|
14
|
+
"coverage": "forge coverage --match-path \"./src/*.sol\" --report lcov --report summary",
|
|
15
|
+
"deploy:mainnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks mainnets",
|
|
16
|
+
"deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets",
|
|
17
|
+
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-router-terminal-v6'"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@bananapus/core-v6": "^0.0.4",
|
|
21
|
+
"@openzeppelin/contracts": "^5.2.0",
|
|
22
|
+
"@uniswap/permit2": "github:Uniswap/permit2",
|
|
23
|
+
"@uniswap/v3-core": "github:Uniswap/v3-core#0.8",
|
|
24
|
+
"@uniswap/v3-periphery": "github:Uniswap/v3-periphery#0.8",
|
|
25
|
+
"@uniswap/v4-core": "^1.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@sphinx-labs/plugins": "^0.33.2"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import "@bananapus/core-v6/script/helpers/CoreDeploymentLib.sol";
|
|
5
|
+
|
|
6
|
+
import {Sphinx} from "@sphinx-labs/contracts/SphinxPlugin.sol";
|
|
7
|
+
import {Script} from "forge-std/Script.sol";
|
|
8
|
+
|
|
9
|
+
import {JBRouterTerminal} from "../src/JBRouterTerminal.sol";
|
|
10
|
+
import {JBRouterTerminalRegistry} from "../src/JBRouterTerminalRegistry.sol";
|
|
11
|
+
import {IWETH9} from "../src/interfaces/IWETH9.sol";
|
|
12
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
13
|
+
import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
|
14
|
+
import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
|
|
15
|
+
|
|
16
|
+
contract DeployScript is Script, Sphinx {
|
|
17
|
+
/// @notice tracks the deployment of the core contracts for the chain we are deploying to.
|
|
18
|
+
CoreDeployment core;
|
|
19
|
+
|
|
20
|
+
/// @notice the salts that are used to deploy the contracts.
|
|
21
|
+
bytes32 ROUTER_TERMINAL = "JBRouterTerminalV6";
|
|
22
|
+
bytes32 ROUTER_TERMINAL_REGISTRY = "JBRouterTerminalRegistryV6";
|
|
23
|
+
|
|
24
|
+
/// @notice tracks the addresses that are required for the chain we are deploying to.
|
|
25
|
+
address weth;
|
|
26
|
+
address factory;
|
|
27
|
+
address poolManager;
|
|
28
|
+
address permit2;
|
|
29
|
+
address trustedForwarder;
|
|
30
|
+
|
|
31
|
+
function configureSphinx() public override {
|
|
32
|
+
sphinxConfig.projectName = "nana-router-terminal-v6";
|
|
33
|
+
sphinxConfig.mainnets = ["ethereum", "optimism", "base", "arbitrum"];
|
|
34
|
+
sphinxConfig.testnets = ["ethereum_sepolia", "optimism_sepolia", "base_sepolia", "arbitrum_sepolia"];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function run() public {
|
|
38
|
+
// Get the deployment addresses for the nana CORE for this chain.
|
|
39
|
+
// We want to do this outside of the `sphinx` modifier.
|
|
40
|
+
core = CoreDeploymentLib.getDeployment(
|
|
41
|
+
vm.envOr("NANA_CORE_DEPLOYMENT_PATH", string("node_modules/@bananapus/core-v6/deployments/"))
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
trustedForwarder = core.permissions.trustedForwarder();
|
|
45
|
+
|
|
46
|
+
// Permit2 is deployed at the same address on all chains.
|
|
47
|
+
permit2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
|
|
48
|
+
|
|
49
|
+
// Ethereum Mainnet
|
|
50
|
+
if (block.chainid == 1) {
|
|
51
|
+
weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
|
|
52
|
+
factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
|
53
|
+
poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
|
54
|
+
// Ethereum Sepolia
|
|
55
|
+
} else if (block.chainid == 11_155_111) {
|
|
56
|
+
weth = 0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9;
|
|
57
|
+
factory = 0x0227628f3F023bb0B980b67D528571c95c6DaC1c;
|
|
58
|
+
poolManager = 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543;
|
|
59
|
+
// Optimism Mainnet
|
|
60
|
+
} else if (block.chainid == 10) {
|
|
61
|
+
weth = 0x4200000000000000000000000000000000000006;
|
|
62
|
+
factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
|
63
|
+
poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
|
64
|
+
// Base Mainnet
|
|
65
|
+
} else if (block.chainid == 8453) {
|
|
66
|
+
weth = 0x4200000000000000000000000000000000000006;
|
|
67
|
+
factory = 0x33128a8fC17869897dcE68Ed026d694621f6FDfD;
|
|
68
|
+
poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
|
69
|
+
// Optimism Sepolia
|
|
70
|
+
} else if (block.chainid == 11_155_420) {
|
|
71
|
+
weth = 0x4200000000000000000000000000000000000006;
|
|
72
|
+
factory = 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24;
|
|
73
|
+
poolManager = 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543;
|
|
74
|
+
// BASE Sepolia
|
|
75
|
+
} else if (block.chainid == 84_532) {
|
|
76
|
+
weth = 0x4200000000000000000000000000000000000006;
|
|
77
|
+
factory = 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24;
|
|
78
|
+
poolManager = 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543;
|
|
79
|
+
// Arbitrum Mainnet
|
|
80
|
+
} else if (block.chainid == 42_161) {
|
|
81
|
+
weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
|
|
82
|
+
factory = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
|
83
|
+
poolManager = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
|
84
|
+
// Arbitrum Sepolia
|
|
85
|
+
} else if (block.chainid == 421_614) {
|
|
86
|
+
weth = 0x980B62Da83eFf3D4576C647993b0c1D7faf17c73;
|
|
87
|
+
factory = 0x248AB79Bbb9bC29bB72f7Cd42F17e054Fc40188e;
|
|
88
|
+
poolManager = 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543;
|
|
89
|
+
} else {
|
|
90
|
+
revert("Invalid RPC / no juice contracts deployed on this network");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Perform the deployment transactions.
|
|
94
|
+
deploy();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function deploy() public sphinx {
|
|
98
|
+
JBRouterTerminalRegistry registry = new JBRouterTerminalRegistry{salt: ROUTER_TERMINAL_REGISTRY}(
|
|
99
|
+
core.permissions, core.projects, IPermit2(permit2), safeAddress(), trustedForwarder
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
JBRouterTerminal terminal = new JBRouterTerminal{salt: ROUTER_TERMINAL}(
|
|
103
|
+
core.directory,
|
|
104
|
+
core.permissions,
|
|
105
|
+
core.projects,
|
|
106
|
+
core.tokens,
|
|
107
|
+
IPermit2(permit2),
|
|
108
|
+
safeAddress(),
|
|
109
|
+
IWETH9(weth),
|
|
110
|
+
IUniswapV3Factory(factory),
|
|
111
|
+
IPoolManager(poolManager),
|
|
112
|
+
trustedForwarder
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Set the terminal as the default for the registry.
|
|
116
|
+
registry.setDefaultTerminal(terminal);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function _isDeployed(bytes32 salt, bytes memory creationCode, bytes memory arguments) internal view returns (bool) {
|
|
120
|
+
address _deployedTo = vm.computeCreate2Address({
|
|
121
|
+
salt: salt,
|
|
122
|
+
initCodeHash: keccak256(abi.encodePacked(creationCode, arguments)),
|
|
123
|
+
// Arachnid/deterministic-deployment-proxy address.
|
|
124
|
+
deployer: address(0x4e59b44847b379578588920cA78FbF26c0B4956C)
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Return if code is already present at this address.
|
|
128
|
+
return address(_deployedTo).code.length != 0;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {stdJson} from "forge-std/Script.sol";
|
|
5
|
+
import {Vm} from "forge-std/Vm.sol";
|
|
6
|
+
|
|
7
|
+
import {SphinxConstants, NetworkInfo} from "@sphinx-labs/contracts/SphinxConstants.sol";
|
|
8
|
+
|
|
9
|
+
import {IJBRouterTerminal} from "../../src/interfaces/IJBRouterTerminal.sol";
|
|
10
|
+
import {IJBRouterTerminalRegistry} from "../../src/interfaces/IJBRouterTerminalRegistry.sol";
|
|
11
|
+
|
|
12
|
+
struct RouterTerminalDeployment {
|
|
13
|
+
IJBRouterTerminal terminal;
|
|
14
|
+
IJBRouterTerminalRegistry registry;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
library RouterTerminalDeploymentLib {
|
|
18
|
+
// Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D.
|
|
19
|
+
address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
|
|
20
|
+
Vm internal constant vm = Vm(VM_ADDRESS);
|
|
21
|
+
|
|
22
|
+
function getDeployment(string memory path) internal returns (RouterTerminalDeployment memory deployment) {
|
|
23
|
+
// get chainId for which we need to get the deployment.
|
|
24
|
+
uint256 chainId = block.chainid;
|
|
25
|
+
|
|
26
|
+
// Deploy to get the constants.
|
|
27
|
+
// TODO: get constants without deploy.
|
|
28
|
+
SphinxConstants sphinxConstants = new SphinxConstants();
|
|
29
|
+
NetworkInfo[] memory networks = sphinxConstants.getNetworkInfoArray();
|
|
30
|
+
|
|
31
|
+
for (uint256 _i; _i < networks.length; _i++) {
|
|
32
|
+
if (networks[_i].chainId == chainId) {
|
|
33
|
+
return getDeployment(path, networks[_i].name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
revert("ChainID is not (currently) supported by Sphinx.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getDeployment(
|
|
41
|
+
string memory path,
|
|
42
|
+
string memory network_name
|
|
43
|
+
)
|
|
44
|
+
internal
|
|
45
|
+
view
|
|
46
|
+
returns (RouterTerminalDeployment memory deployment)
|
|
47
|
+
{
|
|
48
|
+
deployment.terminal = IJBRouterTerminal(
|
|
49
|
+
_getDeploymentAddress(path, "nana-router-terminal-v6", network_name, "JBRouterTerminal")
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
deployment.registry = IJBRouterTerminalRegistry(
|
|
53
|
+
_getDeploymentAddress(path, "nana-router-terminal-v6", network_name, "JBRouterTerminalRegistry")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// @notice Get the address of a contract that was deployed by the Deploy script.
|
|
58
|
+
/// @dev Reverts if the contract was not found.
|
|
59
|
+
/// @param path The path to the deployment file.
|
|
60
|
+
/// @param contractName The name of the contract to get the address of.
|
|
61
|
+
/// @return The address of the contract.
|
|
62
|
+
function _getDeploymentAddress(
|
|
63
|
+
string memory path,
|
|
64
|
+
string memory project_name,
|
|
65
|
+
string memory network_name,
|
|
66
|
+
string memory contractName
|
|
67
|
+
)
|
|
68
|
+
internal
|
|
69
|
+
view
|
|
70
|
+
returns (address)
|
|
71
|
+
{
|
|
72
|
+
string memory deploymentJson =
|
|
73
|
+
vm.readFile(string.concat(path, project_name, "/", network_name, "/", contractName, ".json"));
|
|
74
|
+
return stdJson.readAddress(deploymentJson, ".address");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"detectors_to_exclude": "timestamp,uninitialized-local,naming-convention,solc-version,shadowing-local",
|
|
3
|
+
"exclude_informational": true,
|
|
4
|
+
"exclude_low": false,
|
|
5
|
+
"exclude_medium": false,
|
|
6
|
+
"exclude_high": false,
|
|
7
|
+
"disable_color": false,
|
|
8
|
+
"filter_paths": "(mocks/|test/|node_modules/|lib/)",
|
|
9
|
+
"legacy_ast": false
|
|
10
|
+
}
|