@bananapus/router-terminal-v6 0.0.44 → 0.0.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/router-terminal-v6",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,10 +26,10 @@
26
26
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-router-terminal-v6'"
27
27
  },
28
28
  "dependencies": {
29
- "@bananapus/buyback-hook-v6": "^0.0.47",
30
- "@bananapus/core-v6": "^0.0.54",
31
- "@bananapus/permission-ids-v6": "^0.0.22",
32
- "@bananapus/univ4-router-v6": "^0.0.32",
29
+ "@bananapus/buyback-hook-v6": "^0.0.49",
30
+ "@bananapus/core-v6": "^0.0.57",
31
+ "@bananapus/permission-ids-v6": "^0.0.26",
32
+ "@bananapus/univ4-router-v6": "^0.0.34",
33
33
  "@openzeppelin/contracts": "5.6.1",
34
34
  "@uniswap/permit2": "github:Uniswap/permit2#cc56ad0f3439c502c246fc5cfcc3db92bb8b7219",
35
35
  "@uniswap/v3-core": "github:Uniswap/v3-core#6562c52e8f75f0c10f9deaf44861847585fc8129",
@@ -186,10 +186,10 @@ contract DeployScript is Script, Sphinx {
186
186
  deployer: safeAddress()
187
187
  });
188
188
  terminal.setChainSpecificConstants({
189
- wrappedNativeToken: IWETH9(weth),
190
- factory: IUniswapV3Factory(factory),
191
- poolManager: IPoolManager(poolManager),
192
- univ4Hook: address(univ4Router.hook)
189
+ newWrappedNativeToken: IWETH9(weth),
190
+ newFactory: IUniswapV3Factory(factory),
191
+ newPoolManager: IPoolManager(poolManager),
192
+ newUniv4Hook: address(univ4Router.hook)
193
193
  });
194
194
 
195
195
  // Set the terminal as the default for the registry.
@@ -366,7 +366,9 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
366
366
  pure
367
367
  returns (bool exists, bytes memory data)
368
368
  {
369
- return JBMetadataResolver.getDataFor({id: JBMetadataResolver.getId(key, address(router)), metadata: metadata});
369
+ return JBMetadataResolver.getDataFor({
370
+ id: JBMetadataResolver.getId({purpose: key, target: address(router)}), metadata: metadata
371
+ });
370
372
  }
371
373
 
372
374
  /// @notice Check whether two tokens share the same routing representation for the router.
@@ -918,17 +920,17 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
918
920
  if (_isCircularTerminal({router: router, projectId: projectId, terminal: candidateTerminal})) continue;
919
921
 
920
922
  // Isolate each candidate preview so one broken route does not brick the whole search.
921
- try self.previewPayRouteForCandidate(
922
- router,
923
- wrappedNativeToken,
924
- projectId,
925
- tokenIn,
926
- amount,
927
- beneficiary,
928
- metadata,
929
- candidateTokens[i],
930
- candidateTerminal
931
- ) returns (
923
+ try self.previewPayRouteForCandidate({
924
+ router: router,
925
+ wrappedNativeToken: wrappedNativeToken,
926
+ projectId: projectId,
927
+ tokenIn: tokenIn,
928
+ amount: amount,
929
+ beneficiary: beneficiary,
930
+ metadata: metadata,
931
+ tokenOut: candidateTokens[i],
932
+ destTerminal: candidateTerminal
933
+ }) returns (
932
934
  IJBTerminal candidateDestTerminal,
933
935
  address candidateTokenOut,
934
936
  uint256 candidateAmountOut,
@@ -977,9 +979,15 @@ contract JBPayRouteResolver is IJBPayRouteResolver {
977
979
  // No candidate token could be scored — fall back to the router's generic route resolution.
978
980
  // Uses an external self-call (`self.previewFallbackRoute`) so Solidity's try/catch can isolate
979
981
  // reverts from broken terminals or price feeds without bricking the entire best-route preview.
980
- try self.previewFallbackRoute(
981
- router, wrappedNativeToken, projectId, tokenIn, amount, beneficiary, metadata
982
- ) returns (
982
+ try self.previewFallbackRoute({
983
+ routePreviewer: router,
984
+ wrappedNativeToken: wrappedNativeToken,
985
+ destProjectId: projectId,
986
+ tokenIn: tokenIn,
987
+ amountIn: amount,
988
+ beneficiary: beneficiary,
989
+ metadata: metadata
990
+ }) returns (
983
991
  IJBTerminal fallbackDestTerminal,
984
992
  address fallbackTokenOut,
985
993
  uint256 fallbackAmountOut,
@@ -145,7 +145,7 @@ contract JBRouterTerminal is
145
145
 
146
146
  /// @notice The helper contract used to resolve best pay-route previews without bloating router runtime size.
147
147
  /// @dev Deployed in the constructor with chain-same inputs (just `directory` — the resolver does NOT cache
148
- /// `WRAPPED_NATIVE_TOKEN` locally; the router passes it in on every external resolver call as a parameter to
148
+ /// `wrappedNativeToken` locally; the router passes it in on every external resolver call as a parameter to
149
149
  /// avoid an extra external call on each normalization step). Because this router's address is chain-same via
150
150
  /// CREATE2 and the resolver is deployed at the router's nonce 1, the resolver's address is chain-same too.
151
151
  IJBPayRouteResolver internal immutable _PAY_ROUTE_RESOLVER;
@@ -167,23 +167,23 @@ contract JBRouterTerminal is
167
167
  /// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
168
168
  /// constructor inputs are byte-identical on every chain (Uniswap V3 deploys to a different factory address per
169
169
  /// chain).
170
- IUniswapV3Factory public FACTORY;
170
+ IUniswapV3Factory public factory;
171
171
 
172
172
  /// @notice The Uniswap V4 PoolManager. Can be `address(0)` if V4 is not deployed on this chain.
173
173
  /// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
174
174
  /// constructor inputs are byte-identical on every chain.
175
- IPoolManager public POOL_MANAGER;
175
+ IPoolManager public poolManager;
176
176
 
177
177
  /// @notice The canonical Uniswap V4 router hook address used by supported hooked pools.
178
178
  /// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable because
179
179
  /// `JBUniswapV4Hook` inherits Uniswap's `BaseHook -> ImmutableState`, which forces a chain-specific PoolManager
180
180
  /// immutable inside the hook itself — making the hook chain-different by design.
181
- address public UNIV4_HOOK;
181
+ address public univ4Hook;
182
182
 
183
183
  /// @notice The ERC-20 wrapper for the native token.
184
184
  /// @dev Set once by `_DEPLOYER` via `setChainSpecificConstants`. Held as storage rather than immutable so the
185
185
  /// constructor inputs are byte-identical on every chain (WETH/WCELO/etc. differ per chain).
186
- IWETH9 public override WRAPPED_NATIVE_TOKEN;
186
+ IWETH9 public override wrappedNativeToken;
187
187
 
188
188
  //*********************************************************************//
189
189
  // ---------------------- internal stored properties ----------------- //
@@ -393,29 +393,30 @@ contract JBRouterTerminal is
393
393
  }
394
394
 
395
395
  /// @notice One-shot setter for the chain-specific Uniswap and wrapped-native addresses.
396
- /// @dev Callable only by `_DEPLOYER` and only once (when `WRAPPED_NATIVE_TOKEN` is still `address(0)`). After this
396
+ /// @dev Callable only by `_DEPLOYER` and only once (when `wrappedNativeToken` is still `address(0)`). After this
397
397
  /// call all four values are effectively immutable for the contract's lifetime. Mirrors the
398
398
  /// `JBOptimismSuckerDeployer.setChainSpecificConstants` pattern so the contract's CREATE2 inputs stay
399
399
  /// byte-identical across chains and its deployed address is unified.
400
- /// @param wrappedNativeToken The ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum,
400
+ /// @param newWrappedNativeToken The ERC-20 wrapper for the chain's native token (e.g. WETH on Ethereum,
401
401
  /// WCELO on Celo).
402
- /// @param factory The Uniswap V3 factory for pool discovery on this chain.
403
- /// @param poolManager The Uniswap V4 PoolManager on this chain (may be `address(0)` if V4 is not deployed there).
404
- /// @param univ4Hook The canonical Uniswap V4 router hook on this chain.
402
+ /// @param newFactory The Uniswap V3 factory for pool discovery on this chain.
403
+ /// @param newPoolManager The Uniswap V4 PoolManager on this chain (may be `address(0)` if V4 is not deployed
404
+ /// there).
405
+ /// @param newUniv4Hook The canonical Uniswap V4 router hook on this chain.
405
406
  function setChainSpecificConstants(
406
- IWETH9 wrappedNativeToken,
407
- IUniswapV3Factory factory,
408
- IPoolManager poolManager,
409
- address univ4Hook
407
+ IWETH9 newWrappedNativeToken,
408
+ IUniswapV3Factory newFactory,
409
+ IPoolManager newPoolManager,
410
+ address newUniv4Hook
410
411
  )
411
412
  external
412
413
  {
413
414
  if (msg.sender != _DEPLOYER) revert JBRouterTerminal_Unauthorized({caller: msg.sender});
414
- if (address(WRAPPED_NATIVE_TOKEN) != address(0)) revert JBRouterTerminal_AlreadyConfigured();
415
- WRAPPED_NATIVE_TOKEN = wrappedNativeToken;
416
- FACTORY = factory;
417
- POOL_MANAGER = poolManager;
418
- UNIV4_HOOK = univ4Hook;
415
+ if (address(wrappedNativeToken) != address(0)) revert JBRouterTerminal_AlreadyConfigured();
416
+ wrappedNativeToken = newWrappedNativeToken;
417
+ factory = newFactory;
418
+ poolManager = newPoolManager;
419
+ univ4Hook = newUniv4Hook;
419
420
  }
420
421
 
421
422
  /// @notice The Uniswap v3 pool callback where the token transfer is expected to happen.
@@ -451,7 +452,7 @@ contract JBRouterTerminal is
451
452
  /// @param data Encoded swap parameters.
452
453
  /// @return Encoded output amount.
453
454
  function unlockCallback(bytes calldata data) external override returns (bytes memory) {
454
- if (msg.sender != address(POOL_MANAGER)) revert JBRouterTerminal_CallerNotPoolManager(msg.sender);
455
+ if (msg.sender != address(poolManager)) revert JBRouterTerminal_CallerNotPoolManager(msg.sender);
455
456
 
456
457
  // Decode the swap parameters.
457
458
  (
@@ -464,7 +465,7 @@ contract JBRouterTerminal is
464
465
  ) = abi.decode(data, (PoolKey, bool, int256, uint160, uint256, bool));
465
466
 
466
467
  // Execute the swap.
467
- BalanceDelta delta = POOL_MANAGER.swap({
468
+ BalanceDelta delta = poolManager.swap({
468
469
  key: key,
469
470
  params: SwapParams({
470
471
  zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: sqrtPriceLimitX96
@@ -895,7 +896,7 @@ contract JBRouterTerminal is
895
896
  // it on every normalization step.
896
897
  return _PAY_ROUTE_RESOLVER.previewBestPayRoute({
897
898
  router: IJBPayRoutePreviewer(address(this)),
898
- wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN),
899
+ wrappedNativeToken: address(wrappedNativeToken),
899
900
  projectId: projectId,
900
901
  tokenIn: tokenIn,
901
902
  amount: amount,
@@ -1389,7 +1390,7 @@ contract JBRouterTerminal is
1389
1390
  returns (uint256 amountOut)
1390
1391
  {
1391
1392
  // Convert wrapped-native-token addresses to V4's native representation (address(0)) for currency comparison.
1392
- address v4In = normalizedTokenIn == address(WRAPPED_NATIVE_TOKEN) ? address(0) : normalizedTokenIn;
1393
+ address v4In = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
1393
1394
 
1394
1395
  // Determine the V4 swap direction by comparing the input token to currency0 in the pool key.
1395
1396
  bool zeroForOne = Currency.unwrap(key.currency0) == v4In;
@@ -1403,7 +1404,7 @@ contract JBRouterTerminal is
1403
1404
  // The router only reaches this path with uint256 amounts that fit the signed exact-input convention.
1404
1405
  // forge-lint: disable-next-line(unsafe-typecast)
1405
1406
  int256 exactInputAmount = -int256(amount);
1406
- bytes memory result = POOL_MANAGER.unlock(
1407
+ bytes memory result = poolManager.unlock(
1407
1408
  abi.encode(key, zeroForOne, exactInputAmount, sqrtPriceLimitX96, minAmountOut, canUseExistingNativeBalance)
1408
1409
  );
1409
1410
 
@@ -1465,7 +1466,7 @@ contract JBRouterTerminal is
1465
1466
  (bool success,) = refundTo.call{value: refundAmount}("");
1466
1467
  if (!success) {
1467
1468
  _wrapNativeToken(refundAmount);
1468
- IERC20(address(WRAPPED_NATIVE_TOKEN)).safeTransfer(refundTo, refundAmount);
1469
+ IERC20(address(wrappedNativeToken)).safeTransfer({to: refundTo, value: refundAmount});
1469
1470
  }
1470
1471
  } else {
1471
1472
  _transferFrom({from: address(this), to: refundTo, token: tokenIn, amount: refundAmount});
@@ -1508,7 +1509,7 @@ contract JBRouterTerminal is
1508
1509
  // Resolve what token the destination project accepts and which terminal to use.
1509
1510
  (tokenOut, destTerminal) = _PAY_ROUTE_RESOLVER.resolveTokenOut({
1510
1511
  router: IJBPayRoutePreviewer(address(this)),
1511
- wrappedNativeToken: address(WRAPPED_NATIVE_TOKEN),
1512
+ wrappedNativeToken: address(wrappedNativeToken),
1512
1513
  projectId: destProjectId,
1513
1514
  tokenIn: tokenIn,
1514
1515
  metadata: metadata
@@ -1661,13 +1662,13 @@ contract JBRouterTerminal is
1661
1662
  _unwrapNativeToken(amount);
1662
1663
  }
1663
1664
  // Native settlement uses `msg.value` because PoolManager expects ETH to accompany the settle call.
1664
- POOL_MANAGER.settle{value: amount}();
1665
+ poolManager.settle{value: amount}();
1665
1666
  } else {
1666
1667
  // ERC20 settlement requires PoolManager to observe the token first (`sync`), then receive the transfer,
1667
1668
  // then finalize the accounting with `settle`.
1668
- POOL_MANAGER.sync(currency);
1669
- IERC20(Currency.unwrap(currency)).safeTransfer({to: address(POOL_MANAGER), value: amount});
1670
- POOL_MANAGER.settle();
1669
+ poolManager.sync(currency);
1670
+ IERC20(Currency.unwrap(currency)).safeTransfer({to: address(poolManager), value: amount});
1671
+ poolManager.settle();
1671
1672
  }
1672
1673
  }
1673
1674
 
@@ -1676,7 +1677,7 @@ contract JBRouterTerminal is
1676
1677
  /// @param amount The amount of `currency` to take.
1677
1678
  function _takeV4(Currency currency, uint256 amount) internal {
1678
1679
  // Pull the owed output asset into the router before any later wrapping/unwrapping or forwarding logic runs.
1679
- POOL_MANAGER.take({currency: currency, to: address(this), amount: amount});
1680
+ poolManager.take({currency: currency, to: address(this), amount: amount});
1680
1681
 
1681
1682
  // If native token output, wrap it (downstream _handleSwap unwraps if needed).
1682
1683
  if (Currency.unwrap(currency) == address(0)) _wrapNativeToken(amount);
@@ -1726,13 +1727,13 @@ contract JBRouterTerminal is
1726
1727
  if (token == preferredToken) return (token, amount);
1727
1728
 
1728
1729
  // Wrap native tokens when the preferred token is the ERC-20 wrapper.
1729
- if (token == JBConstants.NATIVE_TOKEN && preferredToken == address(WRAPPED_NATIVE_TOKEN)) {
1730
+ if (token == JBConstants.NATIVE_TOKEN && preferredToken == address(wrappedNativeToken)) {
1730
1731
  _wrapNativeToken(amount);
1731
1732
  return (preferredToken, amount);
1732
1733
  }
1733
1734
 
1734
1735
  // Unwrap wrapped native tokens when the preferred token is the native-token sentinel.
1735
- if (token == address(WRAPPED_NATIVE_TOKEN) && preferredToken == JBConstants.NATIVE_TOKEN) {
1736
+ if (token == address(wrappedNativeToken) && preferredToken == JBConstants.NATIVE_TOKEN) {
1736
1737
  _unwrapNativeToken(amount);
1737
1738
  return (preferredToken, amount);
1738
1739
  }
@@ -1744,13 +1745,13 @@ contract JBRouterTerminal is
1744
1745
  /// @notice Wrap native tokens into their ERC-20 wrapped representation.
1745
1746
  /// @param amount The amount of native tokens to wrap.
1746
1747
  function _wrapNativeToken(uint256 amount) internal {
1747
- WRAPPED_NATIVE_TOKEN.deposit{value: amount}();
1748
+ wrappedNativeToken.deposit{value: amount}();
1748
1749
  }
1749
1750
 
1750
1751
  /// @notice Unwrap wrapped native tokens into native tokens.
1751
1752
  /// @param amount The amount of wrapped native tokens to unwrap.
1752
1753
  function _unwrapNativeToken(uint256 amount) internal {
1753
- WRAPPED_NATIVE_TOKEN.withdraw(amount);
1754
+ wrappedNativeToken.withdraw(amount);
1754
1755
  }
1755
1756
 
1756
1757
  //*********************************************************************//
@@ -1763,14 +1764,14 @@ contract JBRouterTerminal is
1763
1764
  /// @param fee The fee tier to query.
1764
1765
  /// @return The pool address, or address(0) if none exists.
1765
1766
  function _getPool(address tokenA, address tokenB, uint24 fee) internal view returns (address) {
1766
- return FACTORY.getPool({tokenA: tokenA, tokenB: tokenB, fee: fee});
1767
+ return factory.getPool({tokenA: tokenA, tokenB: tokenB, fee: fee});
1767
1768
  }
1768
1769
 
1769
1770
  /// @notice Look up the in-range liquidity of a V4 pool.
1770
1771
  /// @param id The pool ID to query.
1771
1772
  /// @return The pool's current in-range liquidity.
1772
1773
  function _getLiquidity(PoolId id) internal view returns (uint128) {
1773
- return POOL_MANAGER.getLiquidity(id);
1774
+ return poolManager.getLiquidity(id);
1774
1775
  }
1775
1776
 
1776
1777
  /// @notice Read slot0 from a V4 pool.
@@ -1784,7 +1785,7 @@ contract JBRouterTerminal is
1784
1785
  view
1785
1786
  returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)
1786
1787
  {
1787
- return POOL_MANAGER.getSlot0(id);
1788
+ return poolManager.getSlot0(id);
1788
1789
  }
1789
1790
 
1790
1791
  /// @notice Look up the primary terminal for a project and token.
@@ -1942,11 +1943,11 @@ contract JBRouterTerminal is
1942
1943
  updatedBestPool = bestPool;
1943
1944
 
1944
1945
  // Exit early on chains where V4 is not deployed.
1945
- if (address(POOL_MANAGER) == address(0)) return updatedBestPool;
1946
+ if (address(poolManager) == address(0)) return updatedBestPool;
1946
1947
 
1947
1948
  // Convert wrapped native token -> address(0) for V4 native representation.
1948
- address v4In = normalizedTokenIn == address(WRAPPED_NATIVE_TOKEN) ? address(0) : normalizedTokenIn;
1949
- address v4Out = normalizedTokenOut == address(WRAPPED_NATIVE_TOKEN) ? address(0) : normalizedTokenOut;
1949
+ address v4In = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
1950
+ address v4Out = normalizedTokenOut == address(wrappedNativeToken) ? address(0) : normalizedTokenOut;
1950
1951
 
1951
1952
  // Sort currencies (currency0 < currency1).
1952
1953
  (address sorted0, address sorted1) = v4In < v4Out ? (v4In, v4Out) : (v4Out, v4In);
@@ -1954,7 +1955,7 @@ contract JBRouterTerminal is
1954
1955
  for (uint256 i; i < 4;) {
1955
1956
  for (uint256 j; j < 2;) {
1956
1957
  // Probe vanilla pools first, then the configured hooked-pool family if one exists.
1957
- IHooks hooks = j == 0 ? IHooks(address(0)) : IHooks(UNIV4_HOOK);
1958
+ IHooks hooks = j == 0 ? IHooks(address(0)) : IHooks(univ4Hook);
1958
1959
  if (j != 0 && address(hooks) == address(0)) {
1959
1960
  unchecked {
1960
1961
  ++j;
@@ -2245,7 +2246,7 @@ contract JBRouterTerminal is
2245
2246
  /// This fallback is an accepted product risk for programmatic integrations that cannot provide an external quote,
2246
2247
  /// but it should be understood as a bounded-convenience path rather than a fully manipulation-resistant one.
2247
2248
  ///
2248
- /// SECURITY NOTE: The spot price read from `POOL_MANAGER.getSlot0(id)` is an instantaneous value
2249
+ /// SECURITY NOTE: The spot price read from `poolManager.getSlot0(id)` is an instantaneous value
2249
2250
  /// that can be manipulated within the same block (e.g. via sandwich attacks or flash loans). Unlike V3 pools,
2250
2251
  /// V4 vanilla pools do not expose a built-in TWAP oracle, so there is no manipulation-resistant price source
2251
2252
  /// available on-chain for automatic quoting.
@@ -2303,7 +2304,7 @@ contract JBRouterTerminal is
2303
2304
  secondsAgos[1] = 0; // End of the window (current block).
2304
2305
 
2305
2306
  // Ask the hook for cumulative tick data over the window. Silently catch if it doesn't support it.
2306
- try IGeomeanOracle(address(key.hooks)).observe(key, secondsAgos) returns (
2307
+ try IGeomeanOracle(address(key.hooks)).observe({key: key, secondsAgos: secondsAgos}) returns (
2307
2308
  int56[] memory tickCumulatives, uint160[] memory
2308
2309
  ) {
2309
2310
  // Guard against malicious/broken hooks returning fewer elements than requested.
@@ -2335,8 +2336,8 @@ contract JBRouterTerminal is
2335
2336
 
2336
2337
  // V4 uses address(0) for native tokens; map the wrapped native token so OracleLibrary token sorting matches the
2337
2338
  // pool.
2338
- normalizedTokenIn = normalizedTokenIn == address(WRAPPED_NATIVE_TOKEN) ? address(0) : normalizedTokenIn;
2339
- normalizedTokenOut = normalizedTokenOut == address(WRAPPED_NATIVE_TOKEN) ? address(0) : normalizedTokenOut;
2339
+ normalizedTokenIn = normalizedTokenIn == address(wrappedNativeToken) ? address(0) : normalizedTokenIn;
2340
+ normalizedTokenOut = normalizedTokenOut == address(wrappedNativeToken) ? address(0) : normalizedTokenOut;
2340
2341
 
2341
2342
  if (!usedTwap) {
2342
2343
  // Without TWAP, instantaneous liquidity and spot price are both JIT-manipulable.
@@ -2403,7 +2404,7 @@ contract JBRouterTerminal is
2403
2404
  /// @return normalizedToken The normalized token address.
2404
2405
  function _normalize(address token) internal view returns (address) {
2405
2406
  // Replace the native-token sentinel with the wrapped native token so both share one routing representation.
2406
- return token == JBConstants.NATIVE_TOKEN ? address(WRAPPED_NATIVE_TOKEN) : token;
2407
+ return token == JBConstants.NATIVE_TOKEN ? address(wrappedNativeToken) : token;
2407
2408
  }
2408
2409
 
2409
2410
  /// @notice Discover a pool and compute the minimum acceptable output for a swap. Uses a user-provided quote if
@@ -151,7 +151,7 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
151
151
  returns (JBAccountingContext memory context)
152
152
  {
153
153
  // Get the terminal for the project (falls back to the threshold-resolved default).
154
- IJBTerminal terminal = _resolvedTerminalOf(projectId);
154
+ IJBTerminal terminal = _requireResolvedTerminalOf(projectId);
155
155
 
156
156
  // Get the accounting context for the token.
157
157
  return terminal.accountingContextForTokenOf({projectId: projectId, token: token});
@@ -167,7 +167,7 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
167
167
  returns (JBAccountingContext[] memory contexts)
168
168
  {
169
169
  // Get the terminal for the project (falls back to the threshold-resolved default).
170
- IJBTerminal terminal = _resolvedTerminalOf(projectId);
170
+ IJBTerminal terminal = _requireResolvedTerminalOf(projectId);
171
171
 
172
172
  // Get the accounting contexts.
173
173
  return terminal.accountingContextsOf(projectId);
@@ -254,7 +254,7 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
254
254
  {
255
255
  // Read the terminal explicitly configured for this project, falling back to the
256
256
  // threshold-resolved default if none is pinned.
257
- IJBTerminal terminal = _resolvedTerminalOf(projectId);
257
+ IJBTerminal terminal = _requireResolvedTerminalOf(projectId);
258
258
 
259
259
  // Forward the preview request unchanged to whichever terminal was resolved above.
260
260
  return terminal.previewPayFor({
@@ -358,14 +358,19 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
358
358
  function _defaultTerminalFor(uint256 projectId) internal view returns (IJBTerminal terminal) {
359
359
  // New projects (created after the most recent setDefaultTerminal) get the current default.
360
360
  if (projectId > defaultTerminalProjectIdThreshold) return defaultTerminal;
361
- // Older projects walk the history: return the FIRST segment whose maxProjectId covers them.
361
+
362
+ // Older projects walk the history. Each segment covers a half-open range
363
+ // `(minProjectIdExclusive, maxProjectId]` — exactly the cohort that was issued while that segment's terminal
364
+ // was the active default. Projects whose IDs pre-date every recorded default (the cold-start cohort)
365
+ // do not match any segment and resolve to `address(0)` — preserving the documented "the default only applies
366
+ // to projects created AFTER it was set" property at registry cold-start.
362
367
  uint256 len = _defaultTerminalHistory.length;
363
368
  for (uint256 i; i < len; ++i) {
364
- if (projectId <= _defaultTerminalHistory[i].maxProjectId) {
365
- return _defaultTerminalHistory[i].terminal;
369
+ DefaultTerminalSegment storage segment = _defaultTerminalHistory[i];
370
+ if (projectId > segment.minProjectIdExclusive && projectId <= segment.maxProjectId) {
371
+ return segment.terminal;
366
372
  }
367
373
  }
368
- // Falls here only if projectId predates the first setDefaultTerminal call (no default ever applied).
369
374
  return IJBTerminal(address(0));
370
375
  }
371
376
 
@@ -382,6 +387,16 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
382
387
  if (terminal == IJBTerminal(address(0))) terminal = _defaultTerminalFor(projectId);
383
388
  }
384
389
 
390
+ /// @notice Resolve the effective terminal for call paths that need to forward into a real terminal.
391
+ /// @dev `terminalOf`/`defaultTerminalFor` may intentionally return zero for the cold-start cohort. Transactional
392
+ /// and passthrough view paths must fail before accepting funds or calling address(0).
393
+ /// @param projectId The project to resolve the terminal for.
394
+ /// @return terminal The project-specific terminal or threshold-resolved default.
395
+ function _requireResolvedTerminalOf(uint256 projectId) internal view returns (IJBTerminal terminal) {
396
+ terminal = _resolvedTerminalOf(projectId);
397
+ if (terminal == IJBTerminal(address(0))) revert JBRouterTerminalRegistry_TerminalNotSet(projectId);
398
+ }
399
+
385
400
  //*********************************************************************//
386
401
  // ---------------------- external transactions ---------------------- //
387
402
  //*********************************************************************//
@@ -417,8 +432,8 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
417
432
  payable
418
433
  override
419
434
  {
420
- // Resolve the terminal that should receive this forwarded add-to-balance call.
421
- IJBTerminal terminal = _resolvedTerminalOf(projectId);
435
+ // Resolve the terminal that should receive this forwarded add-to-balance call before accepting funds.
436
+ IJBTerminal terminal = _requireResolvedTerminalOf(projectId);
422
437
 
423
438
  // Accept the funds for the token.
424
439
  amount = _acceptFundsFor({token: token, amount: amount, metadata: metadata});
@@ -568,8 +583,8 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
568
583
  override
569
584
  returns (uint256 result)
570
585
  {
571
- // Resolve the terminal that should receive this forwarded payment.
572
- IJBTerminal terminal = _resolvedTerminalOf(projectId);
586
+ // Resolve the terminal that should receive this forwarded payment before accepting funds.
587
+ IJBTerminal terminal = _requireResolvedTerminalOf(projectId);
573
588
 
574
589
  // Accept the funds for the token.
575
590
  amount = _acceptFundsFor({token: token, amount: amount, metadata: metadata});
@@ -626,10 +641,19 @@ contract JBRouterTerminalRegistry is IJBRouterTerminalRegistry, JBPermissioned,
626
641
  // default is what unconfigured (and all-future) projects will resolve to.
627
642
  _requireNonCircularTerminalFor({projectId: count, terminal: terminal});
628
643
 
629
- // Snapshot the OUTGOING default so existing projects (ID <= count) continue resolving to
630
- // it, not to the new default. First-ever call has defaultTerminal == 0 and nothing to snapshot.
644
+ // Snapshot the OUTGOING default so projects whose IDs were issued while it was active keep resolving to it.
645
+ // The segment encodes the half-open range `(prevThreshold, currentCount]` i.e. exactly the cohort that was
646
+ // assigned the outgoing default at creation time. The first call ever has `defaultTerminal == 0` and nothing
647
+ // to snapshot; in that case projects whose IDs already existed remain unaffected by the new default, keeping
648
+ // the cold-start cohort outside any retroactive routing change.
631
649
  if (address(defaultTerminal) != address(0)) {
632
- _defaultTerminalHistory.push(DefaultTerminalSegment({maxProjectId: count, terminal: defaultTerminal}));
650
+ _defaultTerminalHistory.push(
651
+ DefaultTerminalSegment({
652
+ minProjectIdExclusive: defaultTerminalProjectIdThreshold,
653
+ maxProjectId: count,
654
+ terminal: defaultTerminal
655
+ })
656
+ );
633
657
  }
634
658
 
635
659
  defaultTerminal = terminal;
@@ -24,7 +24,7 @@ interface IJBPayRoutePreviewer {
24
24
 
25
25
  /// @notice The ERC-20 wrapper for the chain's native token, used for router token normalization.
26
26
  /// @return weth The ERC-20 wrapper for the chain's native token.
27
- function WRAPPED_NATIVE_TOKEN() external view returns (IWETH9 weth);
27
+ function wrappedNativeToken() external view returns (IWETH9 weth);
28
28
 
29
29
  /// @notice Preview the recursive cashout loop the router would use for a project-token input.
30
30
  /// @param destProjectId The destination project the router is trying to pay.
@@ -3,11 +3,17 @@ pragma solidity ^0.8.0;
3
3
 
4
4
  import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
5
5
 
6
- /// @custom:member maxProjectId The highest project ID covered by this entry's `terminal`. Resolution walks the
7
- /// history forward and returns the first entry whose `maxProjectId >= projectId`.
8
- /// @custom:member terminal The default terminal that was current at the time this history entry was captured (i.e.
9
- /// immediately before the subsequent `setDefaultTerminal` call).
6
+ /// @notice A segment of the default-terminal history. Each segment pins a previous default to the half-open range of
7
+ /// project IDs that were created while it was active. Resolution returns the segment whose
8
+ /// `(minProjectIdExclusive, maxProjectId]` window contains the project's ID.
9
+ /// @custom:member minProjectIdExclusive The threshold that was active when this segment's terminal first became the
10
+ /// default. Project IDs strictly greater than this fall within this segment.
11
+ /// @custom:member maxProjectId The threshold that was set when this segment's terminal was overwritten. Project IDs
12
+ /// less than or equal to this fall within this segment.
13
+ /// @custom:member terminal The default terminal that was active for project IDs in
14
+ /// `(minProjectIdExclusive, maxProjectId]`.
10
15
  struct DefaultTerminalSegment {
16
+ uint256 minProjectIdExclusive;
11
17
  uint256 maxProjectId;
12
18
  IJBTerminal terminal;
13
19
  }