@bananapus/router-terminal-v6 0.0.24 → 0.0.25
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/ADMINISTRATION.md +35 -24
- package/ARCHITECTURE.md +51 -99
- package/AUDIT_INSTRUCTIONS.md +61 -336
- package/CHANGELOG.md +59 -0
- package/README.md +78 -186
- package/RISKS.md +36 -7
- package/SKILLS.md +28 -260
- package/STYLE_GUIDE.md +56 -17
- package/USER_JOURNEYS.md +63 -475
- package/package.json +3 -3
- package/references/operations.md +29 -0
- package/references/runtime.md +32 -0
- package/script/Deploy.s.sol +88 -15
- package/script/helpers/RouterTerminalDeploymentLib.sol +27 -8
- package/src/JBPayRouteResolver.sol +888 -0
- package/src/JBRouterTerminal.sol +1024 -378
- package/src/JBRouterTerminalRegistry.sol +77 -35
- package/src/interfaces/IJBForwardingTerminal.sol +13 -0
- package/src/interfaces/IJBPayRoutePreviewer.sol +100 -0
- package/src/interfaces/IJBPayRouteResolver.sol +114 -0
- package/src/interfaces/IJBPayerTracker.sol +2 -2
- package/src/interfaces/IJBRouterTerminal.sol +2 -2
- package/src/interfaces/IJBRouterTerminalRegistry.sol +14 -13
- package/src/interfaces/IWETH9.sol +3 -1
- package/src/libraries/JBSwapLib.sol +10 -2
- package/src/structs/CashOutPathCandidates.sol +19 -0
- package/test/RouterTerminal.t.sol +712 -52
- package/test/RouterTerminalBuybackHookFork.t.sol +402 -0
- package/test/RouterTerminalCashOutFork.t.sol +2 -2
- package/test/RouterTerminalCreditCashout.t.sol +650 -419
- package/test/RouterTerminalERC2771.t.sol +98 -3
- package/test/RouterTerminalFeeCashOutFork.t.sol +2 -2
- package/test/RouterTerminalFork.t.sol +2 -2
- package/test/RouterTerminalMultihopFork.t.sol +2 -2
- package/test/RouterTerminalPreviewFork.t.sol +4 -3
- package/test/RouterTerminalReentrancy.t.sol +16 -18
- package/test/RouterTerminalSandwichFork.t.sol +2 -2
- package/test/TestAuditGaps.sol +84 -20
- package/test/audit/DeployBuybackHookZero.t.sol +419 -0
- package/test/audit/LeftoverRefund.t.sol +100 -19
- package/test/audit/PayerTrackerRefund.t.sol +9 -3
- package/test/audit/Permit2AllowanceFailed.t.sol +37 -10
- package/test/audit/PreviewPrimaryTerminalMismatch.t.sol +239 -0
- package/test/audit/RefundToBeneficiary.t.sol +3 -4
- package/test/audit/RegistryAddToBalancePartialFill.t.sol +9 -1
- package/test/codex/CashOutCircularPrimaryTerminal.t.sol +326 -0
- package/test/codex/CashOutFallbackPrefersRecursiveLoop.t.sol +363 -0
- package/test/codex/RegistrySelfLockDoS.t.sol +72 -0
- package/test/codex/RouterRegistryReceiptMismatch.t.sol +207 -0
- package/test/codex/V4HookedPoolIgnored.t.sol +101 -0
- package/test/codex/V4WethInputUsesStuckEth.t.sol +330 -0
- package/test/fork/V4QuoteAndSettlementFork.t.sol +2 -2
- package/test/invariant/RouterTerminalInvariant.t.sol +2 -3
- package/test/regression/CashOutLoopLimit.t.sol +10 -2
- package/test/regression/RouterTerminalEdgeCases.t.sol +9 -1
- package/CHANGE_LOG.md +0 -316
package/ADMINISTRATION.md
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
Admin privileges and their scope in nana-router-terminal-v6.
|
|
4
4
|
|
|
5
|
+
## At A Glance
|
|
6
|
+
|
|
7
|
+
| Item | Details |
|
|
8
|
+
|------|---------|
|
|
9
|
+
| Scope | Registry-level selection of router terminal implementations plus immutable router-terminal swap and forwarding behavior. |
|
|
10
|
+
| Operators | Registry owner for the global allowlist/default, project owners or `SET_ROUTER_TERMINAL` delegates for per-project selection, and users who must supply valid routing metadata. |
|
|
11
|
+
| Highest-risk actions | Locking a project to the wrong terminal, changing the default terminal without understanding who inherits it, or assuming the router is an accounting-truth surface when it is not. |
|
|
12
|
+
| Recovery posture | Unlocked projects can switch terminals. Locked projects keep the stored terminal choice, so recovery requires moving the project to a different admin path outside the registry. |
|
|
13
|
+
|
|
14
|
+
## Routine Operations
|
|
15
|
+
|
|
16
|
+
- Keep the registry allowlist limited to router terminal implementations you actually want projects to choose from.
|
|
17
|
+
- Change the default terminal only when you are comfortable affecting every project that still relies on fallback resolution.
|
|
18
|
+
- Encourage projects to lock their terminal only after verifying the resolved terminal address and expected routing behavior.
|
|
19
|
+
- For credit-cashout routing, verify the payer has granted the router terminal the needed `TRANSFER_CREDITS` permission before relying on that path.
|
|
20
|
+
|
|
21
|
+
## One-Way Or High-Risk Actions
|
|
22
|
+
|
|
23
|
+
- `lockTerminalFor` is irreversible.
|
|
24
|
+
- The current default terminal cannot be disallowed; the registry owner must move the default first before removing the old implementation from the allowlist.
|
|
25
|
+
|
|
26
|
+
## Recovery Notes
|
|
27
|
+
|
|
28
|
+
- If the default terminal is wrong, update the registry quickly before more projects snapshot it through `lockTerminalFor`.
|
|
29
|
+
- If a project already locked the wrong terminal, the registry cannot unlock it; recovery has to happen by migrating the project's broader terminal setup elsewhere.
|
|
30
|
+
|
|
5
31
|
## Roles
|
|
6
32
|
|
|
7
33
|
### 1. Registry Owner (Ownable)
|
|
@@ -13,18 +39,10 @@ Admin privileges and their scope in nana-router-terminal-v6.
|
|
|
13
39
|
### 2. Project Owner / SET_ROUTER_TERMINAL Delegate
|
|
14
40
|
|
|
15
41
|
**Contract**: `JBRouterTerminalRegistry`
|
|
16
|
-
**Assigned via**: Ownership of the project's ERC-721 NFT (via `JBProjects.ownerOf(projectId)`), or delegation via `JBPermissions` with permission ID `SET_ROUTER_TERMINAL` (
|
|
42
|
+
**Assigned via**: Ownership of the project's ERC-721 NFT (via `JBProjects.ownerOf(projectId)`), or delegation via `JBPermissions` with permission ID `SET_ROUTER_TERMINAL` (30).
|
|
17
43
|
**Scope**: Per-project -- controls which router terminal a specific project uses, and can permanently lock that choice.
|
|
18
44
|
|
|
19
|
-
### 3.
|
|
20
|
-
|
|
21
|
-
**Contract**: `JBRouterTerminal`
|
|
22
|
-
**Assigned via**: Constructor parameter `owner`, transferable via `Ownable.transferOwnership()`.
|
|
23
|
-
**Scope**: Currently unused. `JBRouterTerminal` inherits `Ownable` but does not gate any functions behind `onlyOwner`. The owner exists for potential future use or subclass extensions. The `Ownable.renounceOwnership()` and `Ownable.transferOwnership()` functions are inherited but have no practical effect on the terminal's operation.
|
|
24
|
-
|
|
25
|
-
**Risk note:** While the owner has no current powers over the terminal's operation, the `Ownable` inheritance means a future code change or subclass could introduce `onlyOwner`-gated functions. If the terminal is deployed with a specific owner address, that address retains transfer rights indefinitely.
|
|
26
|
-
|
|
27
|
-
### 4. Credit Cashout Payer (Implicit)
|
|
45
|
+
### 3. Credit Cashout Payer (Implicit)
|
|
28
46
|
|
|
29
47
|
**Contract**: `JBRouterTerminal`
|
|
30
48
|
**Required permission**: `TRANSFER_CREDITS` (permission ID 13) -- must be granted by the payer to the router terminal address for the source project via `JBPermissions`.
|
|
@@ -40,7 +58,7 @@ When a payment is forwarded through the registry, the terminal is resolved as fo
|
|
|
40
58
|
|
|
41
59
|
**Lock semantics:** When `lockTerminalFor()` is called on a project with no explicit terminal, the current default is snapshot into `_terminalOf[projectId]` before locking. The project becomes independent of future default changes.
|
|
42
60
|
|
|
43
|
-
**Disallow interaction:**
|
|
61
|
+
**Disallow interaction:** The registry owner cannot disallow the current default terminal. `disallowTerminal()` reverts with `JBRouterTerminalRegistry_CannotDisallowDefaultTerminal` until `setDefaultTerminal()` has moved the default elsewhere first. Projects relying on the default therefore keep a valid fallback terminal unless the owner explicitly changes that default.
|
|
44
62
|
|
|
45
63
|
## Privileged Functions
|
|
46
64
|
|
|
@@ -49,17 +67,10 @@ When a payment is forwarded through the registry, the terminal is resolved as fo
|
|
|
49
67
|
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
50
68
|
|----------|--------------|---------------|-------|--------------|
|
|
51
69
|
| `allowTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Adds a terminal to the allowlist (`isTerminalAllowed[terminal] = true`). Projects can only use allowlisted terminals. |
|
|
52
|
-
| `disallowTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Removes a terminal from the allowlist.
|
|
70
|
+
| `disallowTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Removes a terminal from the allowlist. Reverts if `terminal` is the current `defaultTerminal`, so the owner must move the default first. Does NOT affect projects that have already locked their terminal or explicitly set another terminal. |
|
|
53
71
|
| `setDefaultTerminal(terminal)` | Registry Owner | `onlyOwner` | Global | Sets the default terminal for all projects that have not set a project-specific terminal. Also auto-allows the terminal. |
|
|
54
|
-
| `setTerminalFor(projectId, terminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (
|
|
55
|
-
| `lockTerminalFor(projectId, expectedTerminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (
|
|
56
|
-
|
|
57
|
-
### JBRouterTerminal
|
|
58
|
-
|
|
59
|
-
| Function | Required Role | Permission ID | Scope | What It Does |
|
|
60
|
-
|----------|--------------|---------------|-------|--------------|
|
|
61
|
-
| `transferOwnership(newOwner)` | Owner | `onlyOwner` (inherited) | Global | Transfers contract ownership. No functions currently gated by ownership. |
|
|
62
|
-
| `renounceOwnership()` | Owner | `onlyOwner` (inherited) | Global | Renounces contract ownership. No functions currently gated by ownership. |
|
|
72
|
+
| `setTerminalFor(projectId, terminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (30) | Per-project | Routes a specific project to a specific allowed terminal. Reverts if the terminal is not allowlisted or if the project's terminal is locked. |
|
|
73
|
+
| `lockTerminalFor(projectId, expectedTerminal)` | Project Owner or Delegate | `SET_ROUTER_TERMINAL` (30) | Per-project | Permanently locks the terminal choice for a project. If no explicit terminal is set, snapshots the current default into `_terminalOf[projectId]`. Reverts with `TerminalMismatch` if the resolved terminal differs from `expectedTerminal` (race condition protection). **Irreversible.** |
|
|
63
74
|
|
|
64
75
|
### Implicit Permission Requirements (not `onlyOwner`, but enforced by external contracts)
|
|
65
76
|
|
|
@@ -76,15 +87,15 @@ The following values are set at deploy time and cannot be changed:
|
|
|
76
87
|
|
|
77
88
|
| Property | Set At | Mutable? | Description |
|
|
78
89
|
|----------|--------|----------|-------------|
|
|
79
|
-
| `PERMISSIONS` | Constructor | No | JB permissions registry for permission checks |
|
|
80
90
|
| `Trusted forwarder` | Constructor | No | ERC-2771 trusted forwarder for meta-transactions |
|
|
81
91
|
| `DIRECTORY` | Constructor | No | JB directory for terminal/controller lookups |
|
|
82
|
-
| `PROJECTS` | Constructor | No | JB project NFT registry |
|
|
83
92
|
| `TOKENS` | Constructor | No | JB token manager for credit transfers and project token lookups |
|
|
84
93
|
| `FACTORY` | Constructor | No | Uniswap V3 factory for pool discovery and callback verification |
|
|
85
94
|
| `POOL_MANAGER` | Constructor | No | Uniswap V4 PoolManager (can be `address(0)` to disable V4) |
|
|
86
95
|
| `PERMIT2` | Constructor | No | Permit2 contract for gasless approvals |
|
|
87
96
|
| `WETH` | Constructor | No | Wrapped ETH contract |
|
|
97
|
+
| `BUYBACK_HOOK` | Constructor | No | Canonical buyback hook whose metadata this router understands |
|
|
98
|
+
| `UNIV4_HOOK` | Constructor | No | Canonical Uniswap V4 hook address searched during hooked-pool discovery |
|
|
88
99
|
| `DEFAULT_TWAP_WINDOW` | Compile-time constant | No | 10 minutes (600 seconds) |
|
|
89
100
|
| `SLIPPAGE_DENOMINATOR` | Compile-time constant | No | 10,000 (basis points) |
|
|
90
101
|
| `_FEE_TIERS` | Storage (initialized) | No | `[3000, 500, 10000, 100]` -- V3 fee tiers |
|
|
@@ -120,7 +131,7 @@ What admins **cannot** do:
|
|
|
120
131
|
- **Modify swap parameters, slippage, or routing logic.** These are controlled by the `JBRouterTerminal` contract, not the registry.
|
|
121
132
|
- **Pause payments.** There is no pause mechanism.
|
|
122
133
|
|
|
123
|
-
### Router Terminal
|
|
134
|
+
### Router Terminal Maintainers Cannot:
|
|
124
135
|
- **Modify swap slippage parameters.** The TWAP window, sigmoid constants, fee tiers, and max slippage are all immutable.
|
|
125
136
|
- **Redirect funds.** The terminal is stateless between transactions and routes payments to whichever terminal the JB directory specifies.
|
|
126
137
|
- **Change the Uniswap factory or PoolManager.** These are immutable constructor parameters.
|
package/ARCHITECTURE.md
CHANGED
|
@@ -1,116 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Architecture
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`nana-router-terminal-v6` lets a payer fund a Juicebox project with a token the project does not directly accept. It discovers the destination token, unwraps or wraps native assets when needed, optionally cashes out upstream JB project tokens, and swaps through the deepest available Uniswap V3 or V4 route before forwarding the final asset to the destination terminal.
|
|
6
|
+
The router is intentionally heuristic: it does not exhaustively search every viable pool for the absolute best execution price.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Boundaries
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
├── interfaces/
|
|
14
|
-
│ ├── IJBPayerTracker.sol
|
|
15
|
-
│ ├── IJBRouterTerminal.sol
|
|
16
|
-
│ ├── IJBRouterTerminalRegistry.sol
|
|
17
|
-
│ └── IWETH9.sol
|
|
18
|
-
├── libraries/
|
|
19
|
-
│ └── JBSwapLib.sol — Uniswap V3/V4 swap helpers, pool discovery
|
|
20
|
-
└── structs/
|
|
21
|
-
└── PoolInfo.sol — Cached pool configuration
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Key Data Flow
|
|
25
|
-
|
|
26
|
-
### Payment Routing
|
|
27
|
-
```
|
|
28
|
-
Payer → JBRouterTerminal.pay(projectId, token, amount)
|
|
29
|
-
│
|
|
30
|
-
├─ Accept funds (msg.value for native, Permit2 pull for ERC-20, or credit transfer)
|
|
31
|
-
│
|
|
32
|
-
├─ If input is a JB project token (credit or ERC-20):
|
|
33
|
-
│ → Cash out recursively via _cashOutLoop (up to 20 hops)
|
|
34
|
-
│ → Reclaimed token becomes the new tokenIn
|
|
35
|
-
│
|
|
36
|
-
├─ Resolve tokenOut: what does the destination project accept?
|
|
37
|
-
│ 1. Metadata override ("routeTokenOut")
|
|
38
|
-
│ 2. Direct acceptance — project already accepts tokenIn
|
|
39
|
-
│ 3. NATIVE ↔ WETH equivalence check
|
|
40
|
-
│ 4. Dynamic discovery — iterate project terminals, find swappable token
|
|
41
|
-
│
|
|
42
|
-
├─ Convert tokenIn → tokenOut via _convert:
|
|
43
|
-
│ ├─ Same token: no-op
|
|
44
|
-
│ ├─ NATIVE ↔ WETH: wrap (WETH.deposit) or unwrap (WETH.withdraw)
|
|
45
|
-
│ └─ Different tokens: Uniswap swap
|
|
46
|
-
│ │
|
|
47
|
-
│ ├─ If native ETH input: held as raw ETH until V3 callback
|
|
48
|
-
│ │ wraps (WETH.deposit) only the amount the pool consumes
|
|
49
|
-
│ │
|
|
50
|
-
│ ├─ Pool discovery (_discoverPool):
|
|
51
|
-
│ │ Search V3 pools across 4 fee tiers (0.3%, 0.05%, 1%, 0.01%)
|
|
52
|
-
│ │ Search V4 pools across same fee tiers (if PoolManager deployed)
|
|
53
|
-
│ │ Compare in-range liquidity across all candidates
|
|
54
|
-
│ │ Select the single pool with highest liquidity
|
|
55
|
-
│ │
|
|
56
|
-
│ ├─ Quote & slippage (_pickPoolAndQuote):
|
|
57
|
-
│ │ 1. User-provided quote (metadata "quoteForSwap") — used as-is
|
|
58
|
-
│ │ 2. V3 fallback: 10-min TWAP via OracleLibrary.consult()
|
|
59
|
-
│ │ 3. V4 fallback: TWAP from oracle hook if available, else spot price from getSlot0()
|
|
60
|
-
│ │ Apply sigmoid slippage: minSlippage + range * impact/(impact+K)
|
|
61
|
-
│ │
|
|
62
|
-
│ ├─ Execute swap via V3 pool.swap() or V4 POOL_MANAGER.unlock()
|
|
63
|
-
│ │
|
|
64
|
-
│ ├─ If native ETH input: wrap any remaining raw ETH (partial fills)
|
|
65
|
-
│ ├─ If native ETH output: unwrap WETH → ETH (WETH.withdraw)
|
|
66
|
-
│ └─ Return leftover input tokens via _resolveRefundWithBackupRecipient (checks msg.sender's IJBPayerTracker.originalPayer() via try-catch, falls back to beneficiary for pay() or _msgSender() for addToBalanceOf())
|
|
67
|
-
│
|
|
68
|
-
├─ Approve destination terminal for output tokens (or set msg.value for native)
|
|
69
|
-
└─ Forward to destTerminal.pay() → return beneficiary token count
|
|
70
|
-
```
|
|
10
|
+
- The router is a terminal-shaped payment adapter, not a canonical accounting terminal.
|
|
11
|
+
- It owns routing, swapping, quoting, and refund behavior.
|
|
12
|
+
- Final accounting still occurs at the destination terminal that actually accepts the routed token.
|
|
13
|
+
- Pool selection is optimized for simple, bounded route discovery, not full best-execution search across all candidate pools.
|
|
71
14
|
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
Caller → JBRouterTerminal.previewPayFor(projectId, token, amount)
|
|
75
|
-
→ Mirror source-of-funds and routing logic in view context
|
|
76
|
-
→ If direct, wrap-unwrap, or exact cashout route: forward preview to destination terminal
|
|
77
|
-
→ If swap route: estimate output using the same quote-selection logic used for execution bounds
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Extension Points
|
|
15
|
+
## Main Components
|
|
81
16
|
|
|
82
|
-
|
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
17
|
+
| Component | Responsibility |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `JBRouterTerminal` | Source-token intake, route discovery, swapping, and forwarding |
|
|
20
|
+
| `JBRouterTerminalRegistry` | Project-level selection and locking of router terminal instances |
|
|
21
|
+
| `JBPayRouteResolver` | Helper contract that evaluates pay-route preview candidates without bloating router runtime size |
|
|
22
|
+
| `JBSwapLib` | Pool discovery, quoting, and slippage helpers |
|
|
23
|
+
| `PoolInfo`, `CashOutPathCandidates`, and interfaces | Typed routing metadata and registry/payer integration surfaces |
|
|
88
24
|
|
|
89
|
-
##
|
|
25
|
+
## Runtime Model
|
|
90
26
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
27
|
+
```text
|
|
28
|
+
router pay call
|
|
29
|
+
-> accept native, ERC-20, or JB-token-like input
|
|
30
|
+
-> if input is a project token, recursively cash it out first
|
|
31
|
+
-> resolve the destination token the project can actually receive
|
|
32
|
+
-> pick the best direct, wrap/unwrap, or swap route
|
|
33
|
+
-> execute the route and forward the result to the destination terminal
|
|
34
|
+
-> return any leftover input to the original payer when possible
|
|
35
|
+
```
|
|
96
36
|
|
|
97
|
-
##
|
|
37
|
+
## Critical Invariants
|
|
98
38
|
|
|
99
|
-
|
|
39
|
+
- The router's own accounting context is synthetic. Consumers should not treat it as the source of truth for project accounting.
|
|
40
|
+
- Pool discovery and quote logic must stay aligned between preview and execution paths.
|
|
41
|
+
- Refund resolution is part of correctness, not ergonomics. Partial fills without correct refunds create value leaks.
|
|
42
|
+
- Registry locking is a security feature; it prevents projects from being silently switched to untrusted router implementations.
|
|
43
|
+
- Final forwarded ERC-20 hops are only supported for standard tokens whose destination-terminal pull transfers the full nominal amount without transfer fees or burns.
|
|
44
|
+
- Circular `router -> registry -> same router` forwarding is blocked in the registry, not by teaching the router about registry internals.
|
|
100
45
|
|
|
101
|
-
|
|
46
|
+
## Where Complexity Lives
|
|
102
47
|
|
|
103
|
-
|
|
48
|
+
- The router composes multiple route families: direct, wrap/unwrap, recursive JB cash-out, and DEX swaps.
|
|
49
|
+
- Native-asset handling and refund handling are the most failure-prone parts of the implementation.
|
|
50
|
+
- Liquidity discovery across V3 and V4 is simple to describe but easy to desynchronize between preview and live execution.
|
|
51
|
+
- V4 discovery intentionally searches both vanilla pools and pools using the canonical `UNIV4_HOOK`, because buyback-hook and LP-split integrations rely on that hook-backed oracle path.
|
|
52
|
+
- “Best route” in this system means the best route under the router's discovery heuristic, not a guarantee of globally optimal output across every live pool.
|
|
53
|
+
- Fee-on-transfer or otherwise lossy ERC-20s are only tolerated on ingress where the router can reconcile the received balance delta. They are rejected on the router's final terminal-facing hop. The registry does not perform independent receipt enforcement; it relies on the downstream router terminal to reject lossy transfers.
|
|
54
|
+
- The preview candidate fanout lives in `JBPayRouteResolver`, but downstream `previewPayFor(...)` calls still originate from the router so payer-sensitive previews match execution context.
|
|
104
55
|
|
|
105
|
-
|
|
56
|
+
## Dependencies
|
|
106
57
|
|
|
107
|
-
|
|
58
|
+
- `nana-core-v6` terminal and directory surfaces
|
|
59
|
+
- Uniswap V3, Uniswap V4, and Permit2
|
|
60
|
+
- Optional `IJBPayerTracker` intermediaries for refund attribution
|
|
108
61
|
|
|
109
|
-
|
|
62
|
+
## Safe Change Guide
|
|
110
63
|
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- `@openzeppelin/contracts` — SafeERC20, ERC2771, Ownable
|
|
64
|
+
- Keep route selection and execution semantics paired. If preview and execution diverge, frontends will misprice user flows.
|
|
65
|
+
- Be cautious with native-token handling; wrap and unwrap edge cases are where routers usually leak value.
|
|
66
|
+
- If you change recursive cash-out behavior, inspect the hop limit and failure modes together.
|
|
67
|
+
- Do not promote the router into a stateful treasury layer.
|
|
68
|
+
- Treat any new convenience path as a new asset-conservation proof obligation.
|