@bananapus/router-terminal-v6 0.0.13 → 0.0.15

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/ARCHITECTURE.md CHANGED
@@ -12,6 +12,7 @@ src/
12
12
  ├── JBRouterTerminalRegistry.sol — Registry mapping projects to router terminal configs
13
13
  ├── interfaces/
14
14
  │ ├── IJBRouterTerminal.sol
15
+ │ ├── IJBRouterTerminalRegistry.sol
15
16
  │ └── IWETH9.sol
16
17
  ├── libraries/
17
18
  │ └── JBSwapLib.sol — Uniswap V3/V4 swap helpers, pool discovery
@@ -29,10 +30,18 @@ Payer → JBRouterTerminal.pay(projectId, token, amount)
29
30
  → If different token:
30
31
  → Compare V3 and V4 pool quotes
31
32
  → Swap via better pool
32
- → Forward swapped tokens to project's terminal
33
+ → Forward swapped tokens to project's terminal
33
34
  → Return token count from destination payment
34
35
  ```
35
36
 
37
+ ### Preview Routing
38
+ ```
39
+ Caller → JBRouterTerminal.previewPayFor(projectId, token, amount)
40
+ → Mirror source-of-funds and routing logic in view context
41
+ → If direct, wrap-unwrap, or exact cashout route: forward preview to destination terminal
42
+ → If swap route: estimate output using the same quote-selection logic used for execution bounds
43
+ ```
44
+
36
45
  ## Extension Points
37
46
 
38
47
  | Point | Interface | Purpose |
@@ -41,6 +50,13 @@ Payer → JBRouterTerminal.pay(projectId, token, amount)
41
50
  | Registry | `IJBRouterTerminalRegistry` | Maps projects to routing configs |
42
51
  | Permit | `IJBPermitTerminal` | Permit2 token approval support |
43
52
 
53
+ ## Composition Boundary
54
+
55
+ The router terminal exposes the `IJBTerminal` surface because it needs to participate in Juicebox routing, but its
56
+ accounting context is intentionally synthetic. `accountingContextForTokenOf()` returns `decimals = 18` for any token,
57
+ and the registry forwards that value unchanged. Treat the router layer as a payment router only, not as an
58
+ accounting-sensitive terminal source for loan sizing, debt normalization, or any other decimals-dependent logic.
59
+
44
60
  ## Dependencies
45
61
  - `@bananapus/core-v6` — Terminal, directory, permissions
46
62
  - `@uniswap/v3-core` + `v3-periphery` — V3 swap routing
package/CHANGE_LOG.md CHANGED
@@ -21,7 +21,7 @@ The main contract was renamed from `JBSwapTerminal` (and `JBSwapTerminal5_1`) to
21
21
 
22
22
  ### 1.4 `IJBSwapTerminal` Interface Removed, Replaced by `IJBRouterTerminal`
23
23
  - **v5 `IJBSwapTerminal`** exposed: `DEFAULT_PROJECT_ID()`, `MAX_TWAP_WINDOW()`, `MIN_TWAP_WINDOW()`, `MIN_DEFAULT_POOL_CARDINALITY()`, `UNCERTAIN_SLIPPAGE_TOLERANCE()`, `SLIPPAGE_DENOMINATOR()`, `twapWindowOf()`, `addDefaultPool()`, `addTwapParamsFor()`.
24
- - **v6 `IJBRouterTerminal`** exposes only: `discoverBestPool()`, `discoverPool()`.
24
+ - **v6 `IJBRouterTerminal`** exposes: `discoverBestPool()`, `discoverPool()`, and `previewPayFor()`.
25
25
  - All pool/TWAP configuration functions are removed (see section 1.5).
26
26
 
27
27
  ### 1.5 Removed: Per-Project Pool Configuration and TWAP Management
@@ -44,7 +44,7 @@ The `SLIPPAGE_DENOMINATOR` constant was kept but changed from `uint160` to `uint
44
44
 
45
45
  ### 1.7 `supportsInterface` Changed
46
46
  - **v5:** Reported support for `IJBTerminal`, `IJBPermitTerminal`, `IERC165`, `IUniswapV3SwapCallback`, `IJBPermissioned`, `IJBSwapTerminal`.
47
- - **v6:** Reports support for `IJBTerminal`, `IJBPermitTerminal`, `IERC165`, `IJBPermissioned` only. No longer advertises `IUniswapV3SwapCallback` or the custom interface.
47
+ - **v6:** Reports support for `IJBTerminal`, `IJBPermitTerminal`, `IJBRouterTerminal`, `IERC165`, and `IJBPermissioned`. It no longer advertises `IUniswapV3SwapCallback`.
48
48
 
49
49
  ### 1.8 Permission ID Changed (Registry)
50
50
  - **v5:** `lockTerminalFor()` and `setTerminalFor()` used `JBPermissionIds.ADD_SWAP_TERMINAL_POOL`.
@@ -121,18 +121,25 @@ New external view functions on `IJBRouterTerminal` for off-chain queries:
121
121
  - `discoverBestPool(tokenIn, tokenOut)` — returns a `PoolInfo` (V3 or V4).
122
122
  - `discoverPool(tokenIn, tokenOut)` — returns only the V3 pool (backwards-compatible helper).
123
123
 
124
- ### 2.10 `Permit2AllowanceFailed` Event
124
+ ### 2.10 `previewPayFor()` Added
125
+ New external view functions on the router surfaces:
126
+ - `JBRouterTerminal.previewPayFor(projectId, token, amount, beneficiary, metadata)` mirrors the router's payment routing logic and forwards the preview to the terminal that would ultimately receive the payment.
127
+ - `JBRouterTerminalRegistry.previewPayFor(projectId, token, amount, beneficiary, metadata)` resolves the router terminal for a project and forwards the preview.
128
+ - Direct routes and wrap-unwrap routes are previewable exactly.
129
+ - Swap routes return best-effort estimates using the same pool discovery and quote-selection logic used for execution bounds.
130
+
131
+ ### 2.11 `Permit2AllowanceFailed` Event
125
132
  In v6, when a Permit2 allowance call fails during `_acceptFundsFor()`, an event `Permit2AllowanceFailed(token, owner, reason)` is emitted (inherited from `IJBPermitTerminal`), and the payment continues using fallback transfer. In v5, the failure was silently swallowed with an empty `catch`.
126
133
 
127
- ### 2.11 Fee-on-Transfer Token Handling
134
+ ### 2.12 Fee-on-Transfer Token Handling
128
135
  - **v5 `_acceptFundsFor`:** Returned `IERC20(token).balanceOf(address(this))` after transfer — would include any pre-existing balance.
129
136
  - **v6 `_acceptFundsFor`:** Uses balance-delta pattern (`balanceAfter - balanceBefore`) to accurately measure tokens received.
130
137
 
131
- ### 2.12 Partial-Fill Leftover Handling via Balance Delta
138
+ ### 2.13 Partial-Fill Leftover Handling via Balance Delta
132
139
  - **v5 `_handleTokenTransfersAndSwap`:** Measured leftovers as the full `balanceOf(normalizedTokenIn)` — could include pre-existing balances.
133
140
  - **v6 `_handleSwap`:** Snapshots `balanceBefore` and uses `balanceAfter - balanceBefore` for accurate leftover calculation.
134
141
 
135
- ### 2.13 `PERMIT2` Exposed on `IJBRouterTerminalRegistry` Interface
142
+ ### 2.14 `PERMIT2` Exposed on `IJBRouterTerminalRegistry` Interface
136
143
  - **v5:** `PERMIT2` was an immutable on the registry contract but not exposed on the `IJBSwapTerminalRegistry` interface.
137
144
  - **v6:** `PERMIT2()` is declared on the `IJBRouterTerminalRegistry` interface.
138
145
 
package/README.md CHANGED
@@ -2,25 +2,40 @@
2
2
 
3
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
4
 
5
+ This contract family is a routing surface, not an accounting-truth surface. `JBRouterTerminal` synthesizes
6
+ best-effort `JBAccountingContext` values for routing discovery: native tokens use `18` decimals, ERC-20s probe
7
+ `IERC20Metadata.decimals()` when available, and broken or non-standard tokens fall back to `18`. The registry simply
8
+ forwards that context. Do not reuse the router terminal or router terminal registry as an accounting-sensitive terminal
9
+ source for lending or other logic that relies on real token decimals.
10
+
5
11
  _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
12
 
7
13
  ## Architecture
8
14
 
9
15
  | Contract | Description |
10
16
  |----------|-------------|
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, cashing out JB project tokens if the input is a project token, or forwarding directly if the token is already accepted. Uses TWAP oracle (V3) or spot price (V4) for automatic slippage protection when the caller does not provide a quote. Implements `IJBTerminal`, `IJBPermitTerminal`, `IUniswapV3SwapCallback`, and `IUnlockCallback`. |
12
- | `JBRouterTerminalRegistry` | A proxy terminal that delegates `pay` and `addToBalanceOf` to a per-project or default `JBRouterTerminal` instance. Project owners can choose which router terminal they use, and optionally lock that choice permanently. Implements `IJBTerminal` via `IJBRouterTerminalRegistry`. |
17
+ | `JBRouterTerminal` | Core terminal. Accepts any token via `pay` or `addToBalanceOf`, previews payment routes via `previewPayFor`, discovers the destination project's accepted token, and routes there -- swapping through Uniswap V3 or V4 pools if needed, cashing out JB project tokens if the input is a project token, or forwarding directly if the token is already accepted. Uses TWAP oracle (V3) or spot price (V4) for automatic slippage protection when the caller does not provide a quote. Implements `IJBTerminal`, `IJBPermitTerminal`, `IUniswapV3SwapCallback`, `IUnlockCallback`, and `IJBRouterTerminal`. |
18
+ | `JBRouterTerminalRegistry` | A proxy terminal that delegates `pay`, `previewPayFor`, and `addToBalanceOf` to a per-project or default `JBRouterTerminal` instance. Project owners can choose which router terminal they use, and optionally lock that choice permanently. Implements `IJBTerminal` and the extra registry management surface via `IJBRouterTerminalRegistry`. |
13
19
 
14
20
  ## How It Works
15
21
 
16
22
  1. A payer calls `pay(projectId, token, amount, ...)` with any token.
17
23
  2. The terminal accepts the token (supports ERC-20 approvals, Permit2, and credit transfers).
18
24
  3. If the input is a JB project token (ERC-20 or credits), it recursively cashes out through the source project's terminals until reaching a base token.
19
- 4. It resolves which token the destination project accepts, checking: metadata override (`routeTokenOut`), direct acceptance, NATIVE/WETH equivalence, then dynamic pool discovery across all terminals.
25
+ 4. It resolves which token the destination project accepts, checking: metadata override (`routeTokenOut`), direct acceptance only if the chosen terminal exposes non-empty accounting contexts for the project, NATIVE/WETH equivalence, then dynamic pool discovery across all terminals.
20
26
  5. If the resolved token differs from the input, it converts -- wrapping/unwrapping ETH/WETH, or swapping through the best Uniswap V3 or V4 pool.
21
27
  6. Slippage protection: the caller can pass a minimum output quote in metadata (`quoteForSwap` key), or the terminal calculates one using TWAP (V3) or spot price (V4) with a dynamic sigmoid slippage tolerance based on estimated price impact.
22
28
  7. The output tokens are forwarded to the project's primary terminal via `terminal.pay(...)` or `terminal.addToBalanceOf(...)`.
23
29
 
30
+ ### Previewing Payments
31
+
32
+ `previewPayFor(...)` mirrors the router's payment routing logic and forwards the preview to the terminal that would ultimately receive the payment. When a swap is required, it returns the router's best estimate using the same pool-discovery and quote-selection logic used to derive execution bounds.
33
+
34
+ - Direct routes are exact.
35
+ - Native/WETH wrap-unwrap routes are exact.
36
+ - Cashout-only routes are exact when the downstream cashout terminal exposes an exact preview surface.
37
+ - Swap routes return best-effort estimates based on current pool state and any caller-provided `quoteForSwap` metadata.
38
+
24
39
  ```mermaid
25
40
  sequenceDiagram
26
41
  participant Frontend client
@@ -104,6 +119,7 @@ Key `foundry.toml` settings:
104
119
  - `solc = '0.8.26'`
105
120
  - `evm_version = 'cancun'` (required for Uniswap V4's transient storage)
106
121
  - `optimizer_runs = 200`
122
+ - `via_ir = true` (required for `JBRouterTerminal` to fit under EIP-170)
107
123
  - `fuzz.runs = 4096`
108
124
  - `invariant.runs = 1024`, `invariant.depth = 100`
109
125
 
@@ -129,7 +145,8 @@ nana-router-terminal-v6/
129
145
  └── test/
130
146
  ├── RouterTerminal.t.sol # Unit tests (mocked dependencies)
131
147
  ├── RouterTerminalRegistry.t.sol # Registry unit tests
132
- └── RouterTerminalFork.t.sol # Fork tests against mainnet Uniswap pools
148
+ ├── RouterTerminalFork.t.sol # Fork tests against mainnet Uniswap pools
149
+ └── RouterTerminalPreviewFork.t.sol # Fork parity tests for previewPayFor
133
150
  ```
134
151
 
135
152
  ## Payment Metadata
package/RISKS.md CHANGED
@@ -23,6 +23,13 @@
23
23
  - **Fee-on-transfer tokens unsupported.** `_acceptFundsFor` in the terminal uses balance-delta, but the registry does NOT. Fee-on-transfer tokens through the registry will mismatch.
24
24
  - **Credit cashout path.** `_acceptFundsFor` processes `cashOutSource` metadata to transfer credits from `_msgSender()`. Requires `TRANSFER_CREDITS` permission. If a user has this permission set broadly, any caller through the trusted forwarder could drain their credits.
25
25
  - **Registry owner.** Controls which terminals are allowlisted and sets the global default. Disallowing a terminal clears the default if it matches but does NOT clear per-project terminal settings already set to the disallowed terminal.
26
+ - **Synthetic accounting contexts.** `JBRouterTerminal.accountingContextForTokenOf()` uses best-effort decimals for
27
+ routing discovery: native tokens use `18`, ERC-20s probe `IERC20Metadata.decimals()` when available, and broken or
28
+ non-standard tokens fall back to `18`. `JBRouterTerminalRegistry` simply forwards that context. This is safe for
29
+ routing discovery but unsafe for integrations that treat the router or registry as a truthful accounting source for
30
+ non-18-decimal assets. Lending and debt-normalization flows must point at a real terminal, not the router layer.
31
+ The router now refuses to treat a primary terminal as direct acceptance unless that terminal also exposes non-empty
32
+ accounting contexts for the project, so router-stack terminals do not win the direct-forward fast path.
26
33
 
27
34
  ## 4. DoS Vectors
28
35
 
package/SKILLS.md CHANGED
@@ -8,8 +8,8 @@ Accept payments in any ERC-20 token (or native ETH), dynamically discover what t
8
8
 
9
9
  | Contract | Role |
10
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, cashes out JB project tokens, forwards to the primary terminal. Implements `IJBTerminal`, `IJBPermitTerminal`, `IUniswapV3SwapCallback`, `IUnlockCallback`, `IJBRouterTerminal`. |
12
- | `JBRouterTerminalRegistry` | Proxy terminal routing `pay`/`addToBalanceOf` to a per-project or default `JBRouterTerminal`. Project owners can set and lock their terminal choice. Implements `IJBTerminal` via `IJBRouterTerminalRegistry`. |
11
+ | `JBRouterTerminal` | Core terminal: accepts any token, previews payment routes with `previewPayFor`, discovers the best route to the destination project's accepted token, swaps via Uniswap V3 or V4, cashes out JB project tokens, and forwards to the primary terminal. Implements `IJBTerminal`, `IJBPermitTerminal`, `IUniswapV3SwapCallback`, `IUnlockCallback`, `IJBRouterTerminal`. |
12
+ | `JBRouterTerminalRegistry` | Proxy terminal routing `pay`, `previewPayFor`, and `addToBalanceOf` to a per-project or default `JBRouterTerminal`. Project owners can set and lock their terminal choice. Implements `IJBTerminal` via `IJBRouterTerminalRegistry`. |
13
13
 
14
14
  ## Key Functions
15
15
 
@@ -18,6 +18,7 @@ Accept payments in any ERC-20 token (or native ETH), dynamically discover what t
18
18
  | Function | What it does |
19
19
  |----------|--------------|
20
20
  | `pay(projectId, token, amount, beneficiary, minReturnedTokens, memo, metadata)` | Accept any token, route to the destination project's accepted token (cashout, swap, wrap/unwrap, or direct), forward to the project's primary terminal. Returns project token count. |
21
+ | `previewPayFor(projectId, token, amount, beneficiary, metadata)` | Preview the terminal and mint result that the current payment route would produce. Swap routes return best-effort estimates using the router's quote-selection logic. |
21
22
  | `addToBalanceOf(projectId, token, amount, shouldReturnHeldFees, memo, metadata)` | Same routing flow as `pay` but calls `terminal.addToBalanceOf(...)` instead of `terminal.pay(...)`. |
22
23
  | `discoverPool(normalizedTokenIn, normalizedTokenOut) -> IUniswapV3Pool` | Search V3 factory for highest-liquidity pool across 4 fee tiers. Returns the V3 pool (or zero address if V4 was better). |
23
24
  | `discoverBestPool(normalizedTokenIn, normalizedTokenOut) -> PoolInfo` | Discover best pool across both V3 and V4, comparing in-range liquidity. Returns `PoolInfo` indicating protocol version and pool details. |
@@ -26,13 +27,14 @@ Accept payments in any ERC-20 token (or native ETH), dynamically discover what t
26
27
  | `currentSurplusOf(...) -> uint256` | Always returns 0. This terminal holds no surplus. |
27
28
  | `uniswapV3SwapCallback(amount0Delta, amount1Delta, data)` | V3 swap callback. Verifies caller is a legitimate pool via the factory. Wraps ETH if needed and transfers input tokens to the pool. |
28
29
  | `unlockCallback(data) -> bytes` | V4 swap callback. Called by PoolManager during `unlock()`. Executes the swap, settles input, takes output, checks slippage. |
29
- | `supportsInterface(interfaceId) -> bool` | Returns true for `IJBTerminal`, `IJBPermitTerminal`, `IERC165`, `IJBPermissioned`. |
30
+ | `supportsInterface(interfaceId) -> bool` | Returns true for `IJBTerminal`, `IJBPermitTerminal`, `IJBRouterTerminal`, `IERC165`, `IJBPermissioned`. |
30
31
 
31
32
  ### JBRouterTerminalRegistry
32
33
 
33
34
  | Function | What it does |
34
35
  |----------|--------------|
35
36
  | `pay(projectId, token, amount, beneficiary, minReturnedTokens, memo, metadata)` | Resolves the terminal for the project (per-project or default), accepts funds, forwards payment. |
37
+ | `previewPayFor(projectId, token, amount, beneficiary, metadata)` | Resolves the terminal for the project (per-project or default) and forwards the payment preview. |
36
38
  | `addToBalanceOf(projectId, token, amount, shouldReturnHeldFees, memo, metadata)` | Same resolution and forwarding but for balance additions. |
37
39
  | `terminalOf(projectId) -> IJBTerminal` | Returns the terminal for the project, or `defaultTerminal` if none is set. |
38
40
  | `setTerminalFor(projectId, terminal)` | Route a project to a specific allowed router terminal. Requires `SET_ROUTER_TERMINAL` permission (ID 28). Reverts if locked or terminal not allowed. |
@@ -50,6 +52,7 @@ Accept payments in any ERC-20 token (or native ETH), dynamically discover what t
50
52
  | Function | What it does |
51
53
  |----------|--------------|
52
54
  | `_route(destProjectId, tokenIn, amount, metadata)` | Core routing logic. Detects JB project tokens, runs `_cashOutLoop` if needed, then resolves output token and converts. |
55
+ | `_previewRoute(destProjectId, tokenIn, amount, metadata)` | View mirror of `_route`. Returns the terminal, token, and amount that the current payment route would use, estimating swap outputs from current quote data when needed. |
53
56
  | `_resolveTokenOut(projectId, tokenIn, metadata)` | Priority: 1) `routeTokenOut` metadata override, 2) direct acceptance, 3) NATIVE/WETH equivalence, 4) `_discoverAcceptedToken`. |
54
57
  | `_discoverAcceptedToken(projectId, tokenIn)` | Iterates all terminals and their accounting contexts for a project. Finds the accepted token with the deepest Uniswap pool. Falls back to the first accepted token if no pool exists. |
55
58
  | `_convert(tokenIn, tokenOut, amount, projectId, metadata)` | No-op if same token, wrap/unwrap for NATIVE/WETH, or swap via `_handleSwap`. |
@@ -175,6 +178,7 @@ Accept payments in any ERC-20 token (or native ETH), dynamically discover what t
175
178
  - **V3 TWAP**: Reverts with `JBRouterTerminal_NoObservationHistory()` when a V3 pool has no observation history. The TWAP window is capped by the pool's oldest observation if shorter than 10 minutes.
176
179
  - **V4 spot price**: V4 vanilla pools have no built-in TWAP oracle. The terminal uses the current spot tick with the same sigmoid slippage formula.
177
180
  - **V4 requires cancun EVM**: Chains without EIP-1153 (transient storage) cannot use V4 routing. If `POOL_MANAGER` is `address(0)`, V4 discovery is skipped entirely.
181
+ - **Preview estimates**: `previewPayFor()` returns exact values for direct and wrap-unwrap routes, and best-effort estimates for swap routes using current pool state or caller-provided quotes.
178
182
  - The `JBRouterTerminalRegistry` handles token custody during delegation -- it transfers tokens from the payer to itself, then approves and forwards to the underlying terminal.
179
183
  - `_msgSender()` (ERC-2771) is used instead of `msg.sender` for meta-transaction compatibility in both contracts.
180
184
  - The `JBSwapLib` library contains slippage tolerance math (sigmoid formula), price impact estimation, and V3-compatible `sqrtPriceLimitX96` calculation. It does not contain swap execution logic.
package/USER_JOURNEYS.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Every path a user or integrated contract can take through the router terminal.
4
4
 
5
+ The journeys below describe payment routing behavior. They should not be read as proof that the router terminal or
6
+ registry is a truthful accounting source for arbitrary tokens. Both surfaces report synthetic accounting contexts with
7
+ `decimals = 18`, so any integration that depends on real token decimals must query a real terminal instead.
8
+
5
9
  ---
6
10
 
7
11
  ## Journey 1: Pay a Project Through the Router Terminal
@@ -162,6 +166,46 @@ Same as Journey 1. Additionally:
162
166
 
163
167
  ---
164
168
 
169
+ ## Journey 2A: Preview a Payment Through the Router Terminal
170
+
171
+ The caller asks what a payment would do without moving funds.
172
+
173
+ ### Entry Point
174
+
175
+ ```solidity
176
+ function previewPayFor(
177
+ uint256 projectId,
178
+ address token,
179
+ uint256 amount,
180
+ address beneficiary,
181
+ bytes calldata metadata
182
+ )
183
+ external
184
+ view
185
+ returns (
186
+ JBRuleset memory ruleset,
187
+ uint256 beneficiaryTokenCount,
188
+ uint256 reservedTokenCount,
189
+ JBPayHookSpecification[] memory hookSpecifications
190
+ )
191
+ ```
192
+
193
+ ### Behavior
194
+
195
+ 1. `_previewAcceptFundsFor()` mirrors the router's source-of-funds logic in view context.
196
+ 2. `_previewRoute()` mirrors `_route()` and determines which terminal would ultimately receive the payment.
197
+ 3. If the route is direct or wrap-unwrap, the router forwards an exact preview to that destination terminal.
198
+ 4. If the route requires a swap, the router estimates the output using current quote data, then forwards that best-effort preview to the destination terminal.
199
+
200
+ ### Exactness Boundary
201
+
202
+ - **Direct forwarding:** exact
203
+ - **Native/WETH wrap-unwrap:** exact
204
+ - **Cashout-only routes:** exact when the downstream cashout terminal exposes an exact preview surface
205
+ - **Swap routes:** best-effort estimates using current pool state and any caller-provided `quoteForSwap`
206
+
207
+ ---
208
+
165
209
  ## Journey 3: Pay a Project Through the Registry
166
210
 
167
211
  The registry resolves which router terminal instance a project uses, then forwards the payment.
@@ -196,6 +240,42 @@ function pay(
196
240
 
197
241
  ---
198
242
 
243
+ ## Journey 3A: Preview a Payment Through the Registry
244
+
245
+ The registry resolves which router terminal instance a project uses, then forwards the preview.
246
+
247
+ ### Entry Point
248
+
249
+ ```solidity
250
+ // On JBRouterTerminalRegistry:
251
+ function previewPayFor(
252
+ uint256 projectId,
253
+ address token,
254
+ uint256 amount,
255
+ address beneficiary,
256
+ bytes calldata metadata
257
+ )
258
+ external
259
+ view
260
+ returns (
261
+ JBRuleset memory ruleset,
262
+ uint256 beneficiaryTokenCount,
263
+ uint256 reservedTokenCount,
264
+ JBPayHookSpecification[] memory hookSpecifications
265
+ )
266
+ ```
267
+
268
+ ### State Changes
269
+
270
+ None. The registry resolves `_terminalOf[projectId]`, falling back to `defaultTerminal`, then forwards `previewPayFor()` to the resolved router terminal.
271
+
272
+ ### Edge Cases
273
+
274
+ - **No terminal set and no default:** The preview will revert when forwarded to `address(0)`.
275
+ - **Estimated downstream route:** The resolved router terminal may return a best-effort swap estimate rather than an execution-exact value.
276
+
277
+ ---
278
+
199
279
  ## Journey 4: Add to a Project's Balance Through the Registry
200
280
 
201
281
  ### Entry Point
package/foundry.toml CHANGED
@@ -2,6 +2,7 @@
2
2
  solc = '0.8.26'
3
3
  evm_version = 'cancun'
4
4
  optimizer_runs = 200
5
+ via_ir = true
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/router-terminal-v6",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-router-terminal-v6'"
18
18
  },
19
19
  "dependencies": {
20
- "@bananapus/core-v6": "^0.0.17",
20
+ "@bananapus/core-v6": "^0.0.23",
21
21
  "@bananapus/permission-ids-v6": "^0.0.10",
22
22
  "@openzeppelin/contracts": "^5.6.1",
23
23
  "@uniswap/permit2": "github:Uniswap/permit2",