@whetstone-research/doppler-sdk 1.0.26 → 1.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -444,9 +444,9 @@ console.log('Token address:', presetResult.tokenAddress);
444
444
 
445
445
  The preset helper seeds three curated curve buckets sized for ~1B token supply targets:
446
446
 
447
- - `low`: ~5% of the sale allocated to a $7.5k-$30k market cap window.
448
- - `medium`: ~12.5% targeting roughly $50k-$150k market caps.
449
- - `high`: ~20% aimed at $250k-$750k market caps.
447
+ - `low`: 50% of the sale allocated to a $0-$3M market cap window.
448
+ - `medium`: 25% targeting roughly $1k-$40M market caps.
449
+ - `high`: 24% aimed at $100k-$1B market caps.
450
450
 
451
451
  Pass `presets` to pick a subset (e.g. `['medium', 'high']`) or provide `overrides` to adjust ticks, positions, or shares for a specific tier. When the selected presets sum to less than 100%, the builder automatically appends a filler curve (using the highest selected tier's shape) so liquidity always covers the full sale. Shares must stay within 0-1e18 and the helper will throw if the total ever exceeds 100%.
452
452
 
@@ -914,6 +914,8 @@ import { Derc20 } from '@whetstone-research/doppler-sdk/evm';
914
914
  const tokenDirect = new Derc20(publicClient, walletClient, tokenAddress);
915
915
  ```
916
916
 
917
+ For a runnable release-focused example covering legacy DERC20, DERC20 V2 schedules, and DopplerERC20V1 partial releases, see [examples/vesting-release.ts](./examples/vesting-release.ts).
918
+
917
919
  ### DopplerERC20V1 Tokens
918
920
 
919
921
  Use the newer DopplerERC20V1 token template by either setting `type: 'dopplerERC20V1'` explicitly or by passing fields such as `maxBalanceLimit` with `balanceLimitEnd`, `controller`, or `excludedFromBalanceLimit`. When selected, the SDK uses the configured `dopplerERC20V1Factory` by default. `withTokenFactory(address)` is a generic factory override and takes precedence, but it must point to a factory compatible with the selected token path and token data ABI. `controller` is optional and defaults to the zero address, set it only if early balance-limit disable should be possible. Standard configs without the specific fields still use the legacy `standard` path, where cliff/allocation vesting routes to legacy DERC20 V2. Keep explicit `type: 'dopplerERC20V1'` when you want its behavior but have no specific fields to infer from.
@@ -1427,6 +1429,10 @@ for (const id of SUPPORTED_CHAIN_IDS) {
1427
1429
  }
1428
1430
  ```
1429
1431
 
1432
+ Robinhood Chain is available as `CHAIN_IDS.ROBINHOOD` (`4663`). The SDK exposes
1433
+ addresses and support checks for it, but does not export a viem chain definition;
1434
+ use your application's chain/client setup when constructing clients.
1435
+
1430
1436
  ## Advanced Usage
1431
1437
 
1432
1438
  ### Custom Vesting Configuration
@@ -1782,7 +1788,7 @@ pnpm dev
1782
1788
 
1783
1789
  The SDK includes comprehensive tests covering:
1784
1790
 
1785
- - **Airlock Whitelisting**: Verifies that all modules are properly whitelisted on Ethereum Mainnet, Monad Mainnet, Base Mainnet, and Base Sepolia
1791
+ - **Airlock Whitelisting**: Verifies that all modules are properly whitelisted on Ethereum Mainnet, Monad Mainnet, Base Mainnet, Base Sepolia, and Robinhood Chain
1786
1792
  - **Multicurve Functionality**: Tests multicurve auction creation and quoting
1787
1793
  - **Token Address Mining**: Tests for generating optimized token addresses
1788
1794
 
@@ -1796,10 +1802,10 @@ pnpm test:whitelisting
1796
1802
  ALCHEMY_API_KEY=your_key_here pnpm test:whitelisting
1797
1803
 
1798
1804
  # Limit to specific whitelist-audit chains when needed
1799
- TEST_CHAINS=mainnet,base,base-sepolia,monad-mainnet pnpm test:whitelisting
1805
+ TEST_CHAINS=mainnet,base,base-sepolia,monad-mainnet,robinhood pnpm test:whitelisting
1800
1806
  ```
1801
1807
 
1802
- The whitelisting suite is scoped to the release-audit chains: Ethereum Mainnet, Monad Mainnet, Base Mainnet, and Base Sepolia.
1808
+ The whitelisting suite is scoped to the release-audit chains: Ethereum Mainnet, Monad Mainnet, Base Mainnet, Base Sepolia, and Robinhood Chain.
1803
1809
 
1804
1810
  Whitelisting test RPC priority is:
1805
1811
 
@@ -1833,7 +1839,7 @@ If you're migrating from `doppler-v3-sdk` or `doppler-v4-sdk`, see our [Migratio
1833
1839
 
1834
1840
  ## Contributing
1835
1841
 
1836
- Contributions are welcome! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
1842
+ Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.
1837
1843
 
1838
1844
  ## License
1839
1845
 
@@ -404,23 +404,23 @@ var DEFAULT_OPENING_DOPPLER_NUM_PD_SLUGS = DEFAULT_PD_SLUGS;
404
404
  var DEFAULT_OPENING_DOPPLER_FEE = FEE_TIERS.HIGH;
405
405
  var DEFAULT_OPENING_DOPPLER_TICK_SPACING = DOPPLER_MAX_TICK_SPACING;
406
406
  var DEFAULT_MULTICURVE_LOWER_TICKS = [
407
- -202100,
408
- -183100,
409
- -167e3
407
+ -887200,
408
+ -222200,
409
+ -176200
410
410
  ];
411
411
  var DEFAULT_MULTICURVE_UPPER_TICKS = [
412
- -188200,
413
- -172100,
414
- -156e3
412
+ -142200,
413
+ -116300,
414
+ -84100
415
415
  ];
416
416
  var DEFAULT_MULTICURVE_NUM_POSITIONS = [11, 11, 11];
417
417
  var DEFAULT_MULTICURVE_MAX_SUPPLY_SHARES = [
418
- viem.parseEther("0.05"),
419
- // 5% for LOW tier
420
- viem.parseEther("0.125"),
421
- // 12.5% for MEDIUM tier
422
- viem.parseEther("0.2")
423
- // 20% for HIGH tier
418
+ viem.parseEther("0.5"),
419
+ // 50% for LOW tier
420
+ viem.parseEther("0.25"),
421
+ // 25% for MEDIUM tier
422
+ viem.parseEther("0.24")
423
+ // 24% for HIGH tier
424
424
  ];
425
425
  var BASIS_POINTS = 1e4;
426
426
  var FLAG_MASK = BigInt(16383);
@@ -464,7 +464,7 @@ var CHAIN_IDS = {
464
464
  BASE: 8453,
465
465
  BASE_SEPOLIA: 84532,
466
466
  INK: 57073,
467
- TEMP: 4663,
467
+ ROBINHOOD: 4663,
468
468
  UNICHAIN: 130,
469
469
  UNICHAIN_SEPOLIA: 1301,
470
470
  MONAD_TESTNET: 10143,
@@ -736,48 +736,50 @@ var ADDRESSES = {
736
736
  weth: "0x4200000000000000000000000000000000000006",
737
737
  uniswapV4Quoter: "0x3972c00f7ed4885e145823eb7c655375d275a1c5"
738
738
  },
739
- [CHAIN_IDS.TEMP]: {
740
- airlock: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].Airlock,
739
+ [CHAIN_IDS.ROBINHOOD]: {
740
+ airlock: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].Airlock,
741
741
  tokenFactory: ZERO_ADDRESS,
742
742
  dopplerERC20V1Factory: getGeneratedAddress(
743
- CHAIN_IDS.TEMP,
743
+ CHAIN_IDS.ROBINHOOD,
744
744
  "DopplerERC20V1Factory"
745
745
  ),
746
746
  dopplerERC20V1Implementation: getGeneratedAddress(
747
- CHAIN_IDS.TEMP,
747
+ CHAIN_IDS.ROBINHOOD,
748
748
  "DopplerERC20V1"
749
749
  ),
750
- doppler404Factory: getGeneratedAddress(CHAIN_IDS.TEMP, "DN404Factory"),
750
+ doppler404Factory: getGeneratedAddress(CHAIN_IDS.ROBINHOOD, "DN404Factory"),
751
751
  v3Initializer: ZERO_ADDRESS,
752
752
  v3Quoter: "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7",
753
- lockableV3Initializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].LockableUniswapV3Initializer,
754
- v4Initializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].UniswapV4Initializer,
755
- dopplerHookInitializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].DopplerHookInitializer,
753
+ lockableV3Initializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].LockableUniswapV3Initializer,
754
+ v4Initializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].UniswapV4Initializer,
755
+ dopplerHookInitializer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].DopplerHookInitializer,
756
756
  rehypeDopplerHookInitializer: getRehypeDopplerHookInitializerAddress(
757
- CHAIN_IDS.TEMP
757
+ CHAIN_IDS.ROBINHOOD
758
758
  ),
759
- rehypeDopplerHook: getRehypeDopplerHookInitializerAddress(CHAIN_IDS.TEMP),
760
- dopplerLens: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].DopplerLensQuoter,
761
- dopplerDeployer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].DopplerDeployer,
759
+ rehypeDopplerHook: getRehypeDopplerHookInitializerAddress(
760
+ CHAIN_IDS.ROBINHOOD
761
+ ),
762
+ dopplerLens: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].DopplerLensQuoter,
763
+ dopplerDeployer: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].DopplerDeployer,
762
764
  poolManager: "0x8366a39cc670b4001a1121b8f6a443a643e40951",
763
765
  v2Migrator: ZERO_ADDRESS,
764
- v2MigratorSplit: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].UniswapV2MigratorSplit,
766
+ v2MigratorSplit: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].UniswapV2MigratorSplit,
765
767
  v4Migrator: ZERO_ADDRESS,
766
- dopplerHookMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].DopplerHookMigrator,
767
- rehypeDopplerHookMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].RehypeDopplerHookMigrator,
768
- noOpMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].NoOpMigrator,
769
- governanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].GovernanceFactory,
770
- noOpGovernanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].NoOpGovernanceFactory,
771
- launchpadGovernanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].LaunchpadGovernanceFactory,
768
+ dopplerHookMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].DopplerHookMigrator,
769
+ rehypeDopplerHookMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].RehypeDopplerHookMigrator,
770
+ noOpMigrator: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].NoOpMigrator,
771
+ governanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].GovernanceFactory,
772
+ noOpGovernanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].NoOpGovernanceFactory,
773
+ launchpadGovernanceFactory: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].LaunchpadGovernanceFactory,
772
774
  streamableFeesLocker: ZERO_ADDRESS,
773
775
  streamableFeesLockerV2: getGeneratedAddress(
774
- CHAIN_IDS.TEMP,
776
+ CHAIN_IDS.ROBINHOOD,
775
777
  "StreamableFeesLockerV2"
776
778
  ),
777
- topUpDistributor: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].TopUpDistributor,
779
+ topUpDistributor: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].TopUpDistributor,
778
780
  universalRouter: "0x8876789976decbfcbbbe364623c63652db8c0904",
779
781
  permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
780
- bundler: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.TEMP].Bundler,
782
+ bundler: GENERATED_DOPPLER_DEPLOYMENTS[CHAIN_IDS.ROBINHOOD].Bundler,
781
783
  weth: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
782
784
  uniswapV2Factory: "0x8bceaa40b9acdfaedf85adf4ff01f5ad6517937f",
783
785
  uniswapV3Factory: "0x1f7d7550b1b028f7571e69a784071f0205fd2efa",
@@ -6675,6 +6677,92 @@ function getLiquidityForAmount1(sqrtRatioAX96, sqrtRatioBX96, amount1) {
6675
6677
  return amount1 * Q96 / diff;
6676
6678
  }
6677
6679
 
6680
+ // src/evm/utils/multicurveLiquidity.ts
6681
+ var MAX_UINT128 = (1n << 128n) - 1n;
6682
+ function getMaxLiquiditySafeMulticurveTickUpper(params) {
6683
+ if (params.tickUpper <= params.tickLower) {
6684
+ throw new Error(
6685
+ `Unable to find a uint128-safe multicurve max tick below ${params.tickUpper}`
6686
+ );
6687
+ }
6688
+ if (isCanonicalCurveLiquiditySafe(params)) return params.tickUpper;
6689
+ for (let candidate = params.tickUpper - params.tickSpacing; candidate > params.tickLower; candidate -= params.tickSpacing) {
6690
+ if (isCanonicalCurveLiquiditySafe({ ...params, tickUpper: candidate })) {
6691
+ return candidate;
6692
+ }
6693
+ }
6694
+ throw new Error(
6695
+ `Unable to find a uint128-safe multicurve max tick below ${params.tickUpper}`
6696
+ );
6697
+ }
6698
+ function isCanonicalCurveLiquiditySafe(params) {
6699
+ return isAdjustedCurveLiquiditySafe({
6700
+ ...params,
6701
+ isToken0: true
6702
+ }) && isAdjustedCurveLiquiditySafe({
6703
+ ...params,
6704
+ tickLower: -params.tickUpper,
6705
+ tickUpper: -params.tickLower,
6706
+ isToken0: false
6707
+ });
6708
+ }
6709
+ function isAdjustedCurveLiquiditySafe(params) {
6710
+ if (params.numPositions <= 0 || params.tickSpacing <= 0) {
6711
+ throw new Error("Multicurve positions and tick spacing must be positive");
6712
+ }
6713
+ const amountPerPosition = params.curveSupply / BigInt(params.numPositions);
6714
+ if (amountPerPosition <= 1n) return true;
6715
+ const amount = amountPerPosition - 1n;
6716
+ const maxLiquidityPerTick = getMaxLiquidityPerTick(params.tickSpacing);
6717
+ const liquidityByTick = /* @__PURE__ */ new Map();
6718
+ const farTick = params.isToken0 ? params.tickUpper : params.tickLower;
6719
+ const closeTick = params.isToken0 ? params.tickLower : params.tickUpper;
6720
+ const spread = params.tickUpper - params.tickLower;
6721
+ const farSqrtPriceX96 = getSqrtRatioAtTick(farTick);
6722
+ for (let i = 0; i < params.numPositions; i++) {
6723
+ const tickDelta = Number(
6724
+ BigInt(i) * BigInt(spread) / BigInt(params.numPositions)
6725
+ );
6726
+ const unalignedTick = params.isToken0 ? closeTick + tickDelta : closeTick - tickDelta;
6727
+ const startingTick = alignMulticurveTick(
6728
+ params.isToken0,
6729
+ unalignedTick,
6730
+ params.tickSpacing
6731
+ );
6732
+ if (startingTick === farTick) continue;
6733
+ const startingSqrtPriceX96 = getSqrtRatioAtTick(startingTick);
6734
+ const liquidity = params.isToken0 ? getLiquidityForAmount0(startingSqrtPriceX96, farSqrtPriceX96, amount) : getLiquidityForAmount1(farSqrtPriceX96, startingSqrtPriceX96, amount);
6735
+ if (liquidity > maxLiquidityPerTick) return false;
6736
+ const tickA = Math.min(farTick, startingTick);
6737
+ const tickB = Math.max(farTick, startingTick);
6738
+ if (!addTickLiquidity(liquidityByTick, tickA, liquidity, maxLiquidityPerTick)) {
6739
+ return false;
6740
+ }
6741
+ if (!addTickLiquidity(liquidityByTick, tickB, liquidity, maxLiquidityPerTick)) {
6742
+ return false;
6743
+ }
6744
+ }
6745
+ return true;
6746
+ }
6747
+ function getMaxLiquidityPerTick(tickSpacing) {
6748
+ const minTick = Math.trunc(MIN_TICK / tickSpacing) * tickSpacing;
6749
+ const maxTick = Math.trunc(MAX_TICK / tickSpacing) * tickSpacing;
6750
+ const numTicks = BigInt((maxTick - minTick) / tickSpacing + 1);
6751
+ return MAX_UINT128 / numTicks;
6752
+ }
6753
+ function addTickLiquidity(liquidityByTick, tick, liquidity, maxLiquidityPerTick) {
6754
+ const updatedLiquidity = (liquidityByTick.get(tick) ?? 0n) + liquidity;
6755
+ if (updatedLiquidity > maxLiquidityPerTick) return false;
6756
+ liquidityByTick.set(tick, updatedLiquidity);
6757
+ return true;
6758
+ }
6759
+ function alignMulticurveTick(isToken0, tick, tickSpacing) {
6760
+ if (isToken0) {
6761
+ return tick < 0 ? Math.trunc((tick - tickSpacing + 1) / tickSpacing) * tickSpacing : Math.trunc(tick / tickSpacing) * tickSpacing;
6762
+ }
6763
+ return tick < 0 ? Math.trunc(tick / tickSpacing) * tickSpacing : Math.trunc((tick + tickSpacing - 1) / tickSpacing) * tickSpacing;
6764
+ }
6765
+
6678
6766
  // src/evm/utils/computeOptimalGamma.ts
6679
6767
  function computeOptimalGamma(startTick, endTick, duration, epochLength, tickSpacing) {
6680
6768
  const totalEpochs = duration / epochLength;
@@ -6710,6 +6798,23 @@ function isToken0Expected(numeraire) {
6710
6798
  }
6711
6799
  }
6712
6800
 
6801
+ // src/evm/utils/beneficiaries.ts
6802
+ function sortBeneficiaries(beneficiaries) {
6803
+ const sorted = [...beneficiaries].sort((a, b) => {
6804
+ const aAddr = a.beneficiary.toLowerCase();
6805
+ const bAddr = b.beneficiary.toLowerCase();
6806
+ return aAddr < bAddr ? -1 : aAddr > bAddr ? 1 : 0;
6807
+ });
6808
+ for (let i = 1; i < sorted.length; i++) {
6809
+ if (sorted[i].beneficiary.toLowerCase() === sorted[i - 1].beneficiary.toLowerCase()) {
6810
+ throw new Error(
6811
+ `Duplicate beneficiary address: ${sorted[i].beneficiary}. Each beneficiary address must be unique \u2014 the contract requires strictly ascending addresses and reverts with UnorderedBeneficiaries() otherwise. Merge the entries into a single beneficiary with the combined shares.`
6812
+ );
6813
+ }
6814
+ }
6815
+ return sorted;
6816
+ }
6817
+
6713
6818
  // src/evm/types.ts
6714
6819
  var NO_OP_ENABLED_CHAIN_IDS = [
6715
6820
  CHAIN_IDS.MAINNET,
@@ -6718,7 +6823,7 @@ var NO_OP_ENABLED_CHAIN_IDS = [
6718
6823
  CHAIN_IDS.BASE_SEPOLIA,
6719
6824
  CHAIN_IDS.UNICHAIN,
6720
6825
  CHAIN_IDS.UNICHAIN_SEPOLIA,
6721
- CHAIN_IDS.TEMP,
6826
+ CHAIN_IDS.ROBINHOOD,
6722
6827
  CHAIN_IDS.MONAD_TESTNET,
6723
6828
  CHAIN_IDS.MONAD_MAINNET
6724
6829
  ];
@@ -6729,7 +6834,7 @@ var LAUNCHPAD_ENABLED_CHAIN_IDS = [
6729
6834
  CHAIN_IDS.MAINNET,
6730
6835
  CHAIN_IDS.BASE,
6731
6836
  CHAIN_IDS.BASE_SEPOLIA,
6732
- CHAIN_IDS.TEMP,
6837
+ CHAIN_IDS.ROBINHOOD,
6733
6838
  CHAIN_IDS.MONAD_MAINNET
6734
6839
  ];
6735
6840
  function isLaunchpadEnabledChain(chainId) {
@@ -7314,7 +7419,7 @@ function buildCurvesFromPresets(params) {
7314
7419
  }
7315
7420
 
7316
7421
  // src/evm/entities/DopplerFactory.ts
7317
- var MAX_UINT128 = (1n << 128n) - 1n;
7422
+ var MAX_UINT1282 = (1n << 128n) - 1n;
7318
7423
  var MAX_PROCEEDS_SPLIT_SHARE = WAD / 2n;
7319
7424
  var DERC20_V1_MAX_PREMINT_WAD = WAD * 8n / 10n;
7320
7425
  var ONE_MILLION = 1000000n;
@@ -8010,11 +8115,7 @@ var DopplerFactory = class {
8010
8115
  const hasBeneficiaries = params.pool.beneficiaries && params.pool.beneficiaries.length > 0;
8011
8116
  let poolInitializerData;
8012
8117
  if (hasBeneficiaries) {
8013
- const sortedBeneficiaries = params.pool.beneficiaries.slice().sort((a, b) => {
8014
- const aAddr = a.beneficiary.toLowerCase();
8015
- const bAddr = b.beneficiary.toLowerCase();
8016
- return aAddr < bAddr ? -1 : aAddr > bAddr ? 1 : 0;
8017
- });
8118
+ const sortedBeneficiaries = sortBeneficiaries(params.pool.beneficiaries);
8018
8119
  poolInitializerData = viem.encodeAbiParameters(
8019
8120
  [
8020
8121
  {
@@ -8094,14 +8195,20 @@ var DopplerFactory = class {
8094
8195
  const poolInitializerAddress = (() => {
8095
8196
  if (hasBeneficiaries) {
8096
8197
  const lockableInitializer = params.modules?.lockableV3Initializer ?? addresses.lockableV3Initializer;
8097
- if (!lockableInitializer) {
8198
+ if (!lockableInitializer || lockableInitializer === ZERO_ADDRESS) {
8098
8199
  throw new Error(
8099
8200
  "Lockable V3 initializer address not configured on this chain. Required when using beneficiaries."
8100
8201
  );
8101
8202
  }
8102
8203
  return lockableInitializer;
8103
8204
  }
8104
- return params.modules?.v3Initializer ?? addresses.v3Initializer;
8205
+ const standardInitializer = params.modules?.v3Initializer ?? addresses.v3Initializer;
8206
+ if (!standardInitializer || standardInitializer === ZERO_ADDRESS) {
8207
+ throw new Error(
8208
+ "UniswapV3Initializer address not configured on this chain. Use beneficiaries for lockable V3 support, provide an override via builder.withV3Initializer(...), or use a chain with standard V3 initializer support."
8209
+ );
8210
+ }
8211
+ return standardInitializer;
8105
8212
  })();
8106
8213
  const liquidityMigratorAddress = this.getMigratorAddress(
8107
8214
  params.migration,
@@ -8516,6 +8623,11 @@ var DopplerFactory = class {
8516
8623
  addresses
8517
8624
  });
8518
8625
  const poolInitializerAddress = params.modules?.v4Initializer ?? addresses.v4Initializer;
8626
+ if (!poolInitializerAddress || poolInitializerAddress === ZERO_ADDRESS) {
8627
+ throw new Error(
8628
+ "UniswapV4Initializer address not configured on this chain. Provide an override via builder.withV4Initializer(...) or use a chain with dynamic auction support."
8629
+ );
8630
+ }
8519
8631
  const liquidityMigratorAddress = this.getMigratorAddress(
8520
8632
  params.migration,
8521
8633
  params.modules
@@ -9846,13 +9958,7 @@ var DopplerFactory = class {
9846
9958
  if (!streamableFees) {
9847
9959
  return "0x";
9848
9960
  }
9849
- const beneficiaryData = [...streamableFees.beneficiaries].sort(
9850
- (a, b) => {
9851
- const addrA = a.beneficiary.toLowerCase();
9852
- const addrB = b.beneficiary.toLowerCase();
9853
- return addrA < addrB ? -1 : addrA > addrB ? 1 : 0;
9854
- }
9855
- );
9961
+ const beneficiaryData = sortBeneficiaries(streamableFees.beneficiaries);
9856
9962
  return viem.encodeAbiParameters(
9857
9963
  [
9858
9964
  { type: "uint24" },
@@ -9877,12 +9983,8 @@ var DopplerFactory = class {
9877
9983
  ]
9878
9984
  );
9879
9985
  case "uniswapV4Split": {
9880
- const beneficiaryData2 = [...config.streamableFees.beneficiaries].sort(
9881
- (a, b) => {
9882
- const addrA = a.beneficiary.toLowerCase();
9883
- const addrB = b.beneficiary.toLowerCase();
9884
- return addrA < addrB ? -1 : addrA > addrB ? 1 : 0;
9885
- }
9986
+ const beneficiaryData2 = sortBeneficiaries(
9987
+ config.streamableFees.beneficiaries
9886
9988
  );
9887
9989
  const proceedsRecipient = config.proceedsSplit?.recipient ?? ZERO_ADDRESS;
9888
9990
  const proceedsShare = config.proceedsSplit?.share ?? 0n;
@@ -9918,12 +10020,8 @@ var DopplerFactory = class {
9918
10020
  "dopplerHook migration cannot set both hook and rehype config. Use exactly one hook mode."
9919
10021
  );
9920
10022
  }
9921
- const beneficiaries = [...dopplerHookConfig.beneficiaries].sort(
9922
- (a, b) => {
9923
- const addrA = a.beneficiary.toLowerCase();
9924
- const addrB = b.beneficiary.toLowerCase();
9925
- return addrA < addrB ? -1 : addrA > addrB ? 1 : 0;
9926
- }
10023
+ const beneficiaries = sortBeneficiaries(
10024
+ dopplerHookConfig.beneficiaries
9927
10025
  );
9928
10026
  let dopplerHookAddress = ZERO_ADDRESS;
9929
10027
  let onInitializationCalldata = "0x";
@@ -10326,15 +10424,12 @@ var DopplerFactory = class {
10326
10424
  }
10327
10425
  const normalizedCurves = this.normalizeMulticurveCurves(
10328
10426
  params.pool.curves,
10329
- params.pool.tickSpacing
10427
+ params.pool.tickSpacing,
10428
+ params.sale.numTokensToSell
10330
10429
  );
10331
10430
  const addresses = getAddresses(this.chainId);
10332
- const sortedBeneficiaries = (params.pool.beneficiaries ?? []).slice().sort(
10333
- (a, b) => {
10334
- const aAddr = a.beneficiary.toLowerCase();
10335
- const bAddr = b.beneficiary.toLowerCase();
10336
- return aAddr < bAddr ? -1 : aAddr > bAddr ? 1 : 0;
10337
- }
10431
+ const sortedBeneficiaries = sortBeneficiaries(
10432
+ params.pool.beneficiaries ?? []
10338
10433
  );
10339
10434
  const initializerMode = this.resolveMulticurveInitializerMode(params);
10340
10435
  const useScheduledInitializer = initializerMode.type === "scheduled";
@@ -10809,7 +10904,7 @@ var DopplerFactory = class {
10809
10904
  /**
10810
10905
  * Normalize user-provided multicurve positions and ensure they satisfy SDK constraints
10811
10906
  */
10812
- normalizeMulticurveCurves(curves, tickSpacing) {
10907
+ normalizeMulticurveCurves(curves, tickSpacing, numTokensToSell) {
10813
10908
  if (tickSpacing <= 0) {
10814
10909
  throw new Error("Tick spacing must be positive");
10815
10910
  }
@@ -10856,25 +10951,19 @@ var DopplerFactory = class {
10856
10951
  if (fallbackTickLower === void 0) {
10857
10952
  throw new Error("Unable to determine fallback multicurve tick range");
10858
10953
  }
10859
- const fallbackTickUpper = this.roundMaxTickDown(tickSpacing);
10860
- if (fallbackTickLower >= fallbackTickUpper) {
10861
- const adjustedLower = fallbackTickUpper - tickSpacing;
10862
- if (adjustedLower < fallbackTickLower) {
10863
- return sanitizedCurves;
10864
- }
10865
- const fallbackCurve2 = {
10866
- tickLower: adjustedLower,
10867
- tickUpper: fallbackTickUpper,
10868
- numPositions: sanitizedCurves[sanitizedCurves.length - 1]?.numPositions ?? 1,
10869
- shares: missingShare
10870
- };
10871
- return [...sanitizedCurves, fallbackCurve2];
10872
- }
10954
+ const fallbackNumPositions = sanitizedCurves[sanitizedCurves.length - 1]?.numPositions ?? 1;
10955
+ const fallbackTickUpper = getMaxLiquiditySafeMulticurveTickUpper({
10956
+ tickLower: fallbackTickLower,
10957
+ tickUpper: this.roundMaxTickDown(tickSpacing),
10958
+ tickSpacing,
10959
+ numPositions: fallbackNumPositions,
10960
+ curveSupply: numTokensToSell * missingShare / WAD
10961
+ });
10873
10962
  const fallbackCurve = {
10874
10963
  // Extend from the most positive user tick out to the maximum supported tick bucket
10875
10964
  tickLower: fallbackTickLower,
10876
10965
  tickUpper: fallbackTickUpper,
10877
- numPositions: sanitizedCurves[sanitizedCurves.length - 1]?.numPositions ?? 1,
10966
+ numPositions: fallbackNumPositions,
10878
10967
  shares: missingShare
10879
10968
  };
10880
10969
  return [...sanitizedCurves, fallbackCurve];
@@ -11394,8 +11483,15 @@ var DopplerFactory = class {
11394
11483
  getMigratorAddress(config, overrides) {
11395
11484
  const addresses = getAddresses(this.chainId);
11396
11485
  switch (config.type) {
11397
- case "uniswapV2":
11398
- return overrides?.v2Migrator ?? addresses.v2Migrator;
11486
+ case "uniswapV2": {
11487
+ const v2Address = overrides?.v2Migrator ?? addresses.v2Migrator;
11488
+ if (!v2Address || v2Address === ZERO_ADDRESS) {
11489
+ throw new Error(
11490
+ "UniswapV2Migrator not deployed on this chain. Use uniswapV2Split migration or provide override via modules.v2Migrator."
11491
+ );
11492
+ }
11493
+ return v2Address;
11494
+ }
11399
11495
  case "uniswapV2Split": {
11400
11496
  const v2SplitAddress = overrides?.v2MigratorSplit ?? addresses.v2MigratorSplit;
11401
11497
  if (!v2SplitAddress || v2SplitAddress === ZERO_ADDRESS) {
@@ -11407,7 +11503,7 @@ var DopplerFactory = class {
11407
11503
  }
11408
11504
  case "uniswapV4": {
11409
11505
  const v4Address = overrides?.v4Migrator ?? addresses.v4Migrator;
11410
- if (v4Address === "0x0000000000000000000000000000000000000000") {
11506
+ if (!v4Address || v4Address === ZERO_ADDRESS) {
11411
11507
  throw new Error(
11412
11508
  "UniswapV4Migrator not deployed on this chain. Use uniswapV2 migration or provide override via modules.v4Migrator."
11413
11509
  );
@@ -11573,7 +11669,7 @@ var DopplerFactory = class {
11573
11669
  if (!allowZero && value === 0n) {
11574
11670
  throw new Error(`${paramName} must be greater than zero`);
11575
11671
  }
11576
- if (value > MAX_UINT128) {
11672
+ if (value > MAX_UINT1282) {
11577
11673
  throw new Error(`${paramName} exceeds uint128 range`);
11578
11674
  }
11579
11675
  }
@@ -18570,9 +18666,16 @@ var MulticurveBuilder = class _MulticurveBuilder {
18570
18666
  tokenDecimals: config.tokenDecimals ?? 18,
18571
18667
  numeraireDecimals: config.numeraireDecimals ?? 18
18572
18668
  });
18669
+ const curveSupply = this.sale.numTokensToSell * curve.shares / WAD;
18670
+ const tickUpper = curve.marketCap.end === "max" ? getMaxLiquiditySafeMulticurveTickUpper({
18671
+ ...curveTicks,
18672
+ tickSpacing,
18673
+ numPositions: curve.numPositions,
18674
+ curveSupply
18675
+ }) : curveTicks.tickUpper;
18573
18676
  curves.push({
18574
18677
  tickLower: curveTicks.tickLower,
18575
- tickUpper: curveTicks.tickUpper,
18678
+ tickUpper,
18576
18679
  numPositions: curve.numPositions,
18577
18680
  shares: curve.shares
18578
18681
  });
@@ -18633,7 +18736,7 @@ var MulticurveBuilder = class _MulticurveBuilder {
18633
18736
  }
18634
18737
  dopplerHook = { ...dopplerHook, farTick };
18635
18738
  }
18636
- const initializer = this.initializer ?? (dopplerHook ? { type: "rehype", config: dopplerHook } : this.schedule ? { type: "scheduled", startTime: this.schedule.startTime } : { type: "standard" });
18739
+ const initializer = this.initializer?.type === "rehype" && dopplerHook ? { type: "rehype", config: dopplerHook } : this.initializer ?? (dopplerHook ? { type: "rehype", config: dopplerHook } : this.schedule ? { type: "scheduled", startTime: this.schedule.startTime } : { type: "standard" });
18637
18740
  if (initializer.type === "scheduled" && dopplerHook) {
18638
18741
  throw new Error(
18639
18742
  "Cannot combine scheduled multicurve with rehype initializer. Use exactly one initializer mode."
@@ -19461,6 +19564,7 @@ exports.getAmount0ForLiquidity = getAmount0ForLiquidity;
19461
19564
  exports.getAmount1ForLiquidity = getAmount1ForLiquidity;
19462
19565
  exports.getLiquidityForAmount0 = getLiquidityForAmount0;
19463
19566
  exports.getLiquidityForAmount1 = getLiquidityForAmount1;
19567
+ exports.getMaxLiquiditySafeMulticurveTickUpper = getMaxLiquiditySafeMulticurveTickUpper;
19464
19568
  exports.getMaxTickRounded = getMaxTickRounded;
19465
19569
  exports.getNearestUsableTick = getNearestUsableTick;
19466
19570
  exports.getSqrtRatioAtTick = getSqrtRatioAtTick;
@@ -19489,6 +19593,7 @@ exports.ratioToTick = ratioToTick;
19489
19593
  exports.rehypeDopplerHookAbi = rehypeDopplerHookAbi;
19490
19594
  exports.rehypeDopplerHookMigratorAbi = rehypeDopplerHookMigratorAbi;
19491
19595
  exports.resolveGasEstimate = resolveGasEstimate;
19596
+ exports.sortBeneficiaries = sortBeneficiaries;
19492
19597
  exports.sqrtPriceX96ToPrice = sqrtPriceX96ToPrice;
19493
19598
  exports.streamableFeesLockerAbi = streamableFeesLockerAbi;
19494
19599
  exports.streamableFeesLockerV2Abi = streamableFeesLockerV2Abi;