@whetstone-research/doppler-sdk 1.0.5 → 1.0.6

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
@@ -88,6 +88,13 @@ const params = new StaticAuctionBuilder()
88
88
  // Optional: specify multiple recipients and amounts
89
89
  // recipients: ['0xTeam...', '0xAdvisor...'],
90
90
  // amounts: [parseEther('50000000'), parseEther('50000000')]
91
+ // Optional: define per-beneficiary schedules on the DERC20 V2 path
92
+ // schedules: [
93
+ // { duration: BigInt(180 * 24 * 60 * 60), cliffDuration: 30 * 24 * 60 * 60 },
94
+ // { duration: BigInt(365 * 24 * 60 * 60), cliffDuration: 90 * 24 * 60 * 60 },
95
+ // ],
96
+ // Optional: if omitted, one schedule is assigned per recipient in order
97
+ // scheduleIds: [0, 1]
91
98
  })
92
99
  .withMigration({ type: 'uniswapV2' })
93
100
  .withUserAddress('0x...')
@@ -98,7 +105,9 @@ console.log('Pool address:', result.poolAddress);
98
105
  console.log('Token address:', result.tokenAddress);
99
106
  ```
100
107
 
101
- If you set `cliffDuration > 0`, the SDK now automatically uses the DERC20 V2 factory and exposes schedule-aware token reads via `sdk.getDerc20V2(tokenAddress)`.
108
+ If you set `cliffDuration > 0` or provide `schedules`, the SDK automatically uses the DERC20 V2 factory and exposes schedule-aware token reads via `sdk.getDerc20V2(tokenAddress)`. When `schedules` is provided, omit `scheduleIds` to assign one schedule per recipient in order, or provide `scheduleIds` to reuse schedules across beneficiaries.
109
+
110
+ For a runnable example, see [examples/multicurve-per-beneficiary-vesting.ts](./examples/multicurve-per-beneficiary-vesting.ts).
102
111
 
103
112
  > **Tick spacing reminder:** When you provide ticks manually via `poolByTicks`, make sure both `startTick` and `endTick` are exact multiples of the fee tier's tick spacing (100→1, 500→10, 3000→60, 10000→200). The SDK now validates this locally and will fail fast if the ticks are misaligned.
104
113
 
@@ -5282,6 +5282,7 @@ var erc20BalanceOfAbi = [
5282
5282
  ];
5283
5283
  var TOKEN_FACTORY_80_ADDRESS2 = "0xf0b5141dd9096254b2ca624dff26024f46087229";
5284
5284
  var DERC20_V2_MIN_VESTING_DURATION = 24 * 60 * 60;
5285
+ var MAX_UINT64 = (1n << 64n) - 1n;
5285
5286
  var DopplerFactory = class {
5286
5287
  publicClient;
5287
5288
  walletClient;
@@ -5293,8 +5294,14 @@ var DopplerFactory = class {
5293
5294
  this.walletClient = walletClient;
5294
5295
  this.chainId = chainId;
5295
5296
  }
5297
+ hasCustomV2Schedules(vesting) {
5298
+ return (vesting?.schedules?.length ?? 0) > 0 || (vesting?.scheduleIds?.length ?? 0) > 0;
5299
+ }
5296
5300
  usesDerc20V2Vesting(vesting) {
5297
- return (vesting?.cliffDuration ?? 0) > 0;
5301
+ if (!vesting) {
5302
+ return false;
5303
+ }
5304
+ return this.hasCustomV2Schedules(vesting) || (vesting.cliffDuration ?? 0) > 0;
5298
5305
  }
5299
5306
  resolveVestingAllocations(args) {
5300
5307
  if (!args.vesting) {
@@ -5311,6 +5318,83 @@ var DopplerFactory = class {
5311
5318
  amounts: [args.sale.initialSupply - args.sale.numTokensToSell]
5312
5319
  };
5313
5320
  }
5321
+ normalizeV2ScheduleId(scheduleId, label) {
5322
+ if (!Number.isFinite(scheduleId) || !Number.isInteger(scheduleId)) {
5323
+ throw new Error(`${label} must be an integer`);
5324
+ }
5325
+ if (!Number.isSafeInteger(scheduleId)) {
5326
+ throw new Error(`${label} must be a safe integer`);
5327
+ }
5328
+ if (scheduleId < 0) {
5329
+ throw new Error(`${label} cannot be negative`);
5330
+ }
5331
+ return BigInt(scheduleId);
5332
+ }
5333
+ validateUint64LikeNumber(value, fieldPath, options = {}) {
5334
+ const { allowZero = true } = options;
5335
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
5336
+ throw new Error(`${fieldPath} must be a finite integer`);
5337
+ }
5338
+ if (!Number.isSafeInteger(value)) {
5339
+ throw new Error(`${fieldPath} must be a safe integer`);
5340
+ }
5341
+ if (value < 0) {
5342
+ throw new Error(`${fieldPath} cannot be negative`);
5343
+ }
5344
+ if (!allowZero && value === 0) {
5345
+ throw new Error(`${fieldPath} must be greater than zero`);
5346
+ }
5347
+ if (BigInt(value) > MAX_UINT64) {
5348
+ throw new Error(`${fieldPath} must fit in uint64`);
5349
+ }
5350
+ }
5351
+ resolveV2VestingSchedules(args) {
5352
+ if (!args.vesting) {
5353
+ return { schedules: [], scheduleIds: [] };
5354
+ }
5355
+ if (!this.hasCustomV2Schedules(args.vesting)) {
5356
+ return {
5357
+ schedules: [
5358
+ {
5359
+ cliff: BigInt(args.vesting.cliffDuration ?? 0),
5360
+ duration: BigInt(args.vesting.duration ?? 0)
5361
+ }
5362
+ ],
5363
+ scheduleIds: Array.from({ length: args.recipientCount }, () => 0n)
5364
+ };
5365
+ }
5366
+ const schedules = args.vesting.schedules?.map((schedule) => ({
5367
+ cliff: BigInt(schedule.cliffDuration ?? 0),
5368
+ duration: BigInt(schedule.duration ?? 0)
5369
+ })) ?? [];
5370
+ const explicitScheduleIds = args.vesting.scheduleIds;
5371
+ if (explicitScheduleIds && explicitScheduleIds.length > 0) {
5372
+ return {
5373
+ schedules,
5374
+ scheduleIds: explicitScheduleIds.map(
5375
+ (scheduleId, index) => this.normalizeV2ScheduleId(
5376
+ scheduleId,
5377
+ `Vesting scheduleIds[${index}]`
5378
+ )
5379
+ )
5380
+ };
5381
+ }
5382
+ if (schedules.length === 1) {
5383
+ return {
5384
+ schedules,
5385
+ scheduleIds: Array.from({ length: args.recipientCount }, () => 0n)
5386
+ };
5387
+ }
5388
+ if (schedules.length === args.recipientCount) {
5389
+ return {
5390
+ schedules,
5391
+ scheduleIds: schedules.map((_, index) => BigInt(index))
5392
+ };
5393
+ }
5394
+ throw new Error(
5395
+ "Vesting schedules must either contain exactly one shared schedule or one schedule per recipient when scheduleIds are omitted"
5396
+ );
5397
+ }
5314
5398
  resolveStandardTokenFactoryMode(args) {
5315
5399
  if (this.usesDerc20V2Vesting(args.vesting)) {
5316
5400
  return "v2";
@@ -5352,12 +5436,10 @@ var DopplerFactory = class {
5352
5436
  "DERC20 V2 implementation address not configured for this chain."
5353
5437
  );
5354
5438
  }
5355
- const schedules = args.vesting === void 0 ? [] : [
5356
- {
5357
- cliff: BigInt(args.vesting.cliffDuration ?? 0),
5358
- duration: BigInt(args.vesting.duration ?? 0)
5359
- }
5360
- ];
5439
+ const { schedules, scheduleIds } = this.resolveV2VestingSchedules({
5440
+ vesting: args.vesting,
5441
+ recipientCount: recipients.length
5442
+ });
5361
5443
  return {
5362
5444
  kind: "v2",
5363
5445
  name: args.token.name,
@@ -5367,7 +5449,7 @@ var DopplerFactory = class {
5367
5449
  yearlyMintRate,
5368
5450
  schedules,
5369
5451
  beneficiaries: recipients,
5370
- scheduleIds: recipients.map(() => 0n),
5452
+ scheduleIds,
5371
5453
  amounts,
5372
5454
  tokenURI: args.token.tokenURI,
5373
5455
  implementation
@@ -8154,18 +8236,10 @@ var DopplerFactory = class {
8154
8236
  }
8155
8237
  const cliffDuration = vesting.cliffDuration ?? 0;
8156
8238
  const duration = vesting.duration ?? 0;
8157
- if (cliffDuration < 0) {
8158
- throw new Error("Vesting cliff duration cannot be negative");
8159
- }
8160
- if (duration < 0) {
8161
- throw new Error("Vesting duration cannot be negative");
8162
- }
8163
- if (cliffDuration > duration) {
8164
- throw new Error("Vesting cliff duration cannot exceed vesting duration");
8165
- }
8166
- if (cliffDuration > 0 && duration > 0 && duration < DERC20_V2_MIN_VESTING_DURATION) {
8239
+ const hasCustomSchedules = this.hasCustomV2Schedules(vesting);
8240
+ if (hasCustomSchedules && (cliffDuration > 0 || duration > 0)) {
8167
8241
  throw new Error(
8168
- `Vesting duration must be 0 or at least ${DERC20_V2_MIN_VESTING_DURATION} seconds when using cliffs`
8242
+ "Use vesting.schedules instead of top-level duration/cliffDuration when configuring multiple vesting schedules"
8169
8243
  );
8170
8244
  }
8171
8245
  if (vesting.recipients && vesting.amounts) {
@@ -8184,11 +8258,79 @@ var DopplerFactory = class {
8184
8258
  `Total vesting amount (${totalVested}) exceeds available tokens (${availableForVesting})`
8185
8259
  );
8186
8260
  }
8261
+ } else {
8262
+ const vestedAmount = sale.initialSupply - sale.numTokensToSell;
8263
+ if (vestedAmount <= 0n) {
8264
+ throw new Error("No tokens available for vesting");
8265
+ }
8266
+ }
8267
+ if (hasCustomSchedules) {
8268
+ const schedules = vesting.schedules;
8269
+ if (!schedules || schedules.length === 0) {
8270
+ throw new Error(
8271
+ "Vesting schedules are required when using scheduleIds or multiple vesting schedules"
8272
+ );
8273
+ }
8274
+ const recipientCount = vesting.recipients && vesting.amounts ? vesting.recipients.length : 1;
8275
+ if (vesting.scheduleIds) {
8276
+ if (vesting.scheduleIds.length !== recipientCount) {
8277
+ throw new Error(
8278
+ "Vesting scheduleIds array must have the same length as vesting recipients"
8279
+ );
8280
+ }
8281
+ for (const [index, scheduleId] of vesting.scheduleIds.entries()) {
8282
+ this.validateUint64LikeNumber(
8283
+ scheduleId,
8284
+ `Vesting scheduleIds[${index}]`
8285
+ );
8286
+ if (scheduleId >= schedules.length) {
8287
+ throw new Error(
8288
+ `Vesting scheduleIds[${index}] references missing schedule ${scheduleId}`
8289
+ );
8290
+ }
8291
+ }
8292
+ } else if (schedules.length !== 1 && schedules.length !== recipientCount) {
8293
+ throw new Error(
8294
+ "Vesting schedules must either contain exactly one shared schedule or one schedule per recipient when scheduleIds are omitted"
8295
+ );
8296
+ }
8297
+ for (const [index, schedule] of schedules.entries()) {
8298
+ const scheduleCliff = schedule.cliffDuration ?? 0;
8299
+ const scheduleDuration = schedule.duration ?? 0;
8300
+ this.validateUint64LikeNumber(
8301
+ scheduleCliff,
8302
+ `Vesting schedules[${index}].cliffDuration`
8303
+ );
8304
+ this.validateUint64LikeNumber(
8305
+ scheduleDuration,
8306
+ `Vesting schedules[${index}].duration`
8307
+ );
8308
+ if (scheduleCliff > scheduleDuration) {
8309
+ throw new Error(
8310
+ `Vesting schedules[${index}].cliffDuration cannot exceed duration`
8311
+ );
8312
+ }
8313
+ if (scheduleCliff > 0 && scheduleDuration > 0 && scheduleDuration < DERC20_V2_MIN_VESTING_DURATION) {
8314
+ throw new Error(
8315
+ `Vesting schedules[${index}].duration must be 0 or at least ${DERC20_V2_MIN_VESTING_DURATION} seconds when using cliffs`
8316
+ );
8317
+ }
8318
+ }
8187
8319
  return;
8188
8320
  }
8189
- const vestedAmount = sale.initialSupply - sale.numTokensToSell;
8190
- if (vestedAmount <= 0n) {
8191
- throw new Error("No tokens available for vesting");
8321
+ if (cliffDuration < 0) {
8322
+ throw new Error("Vesting cliff duration cannot be negative");
8323
+ }
8324
+ if (duration < 0) {
8325
+ throw new Error("Vesting duration cannot be negative");
8326
+ }
8327
+ if (cliffDuration > duration) {
8328
+ throw new Error("Vesting cliff duration cannot exceed vesting duration");
8329
+ }
8330
+ if (cliffDuration > 0 && duration > 0 && duration < DERC20_V2_MIN_VESTING_DURATION) {
8331
+ throw new Error(
8332
+ `Vesting duration must be 0 or at least ${DERC20_V2_MIN_VESTING_DURATION} seconds when using cliffs`
8333
+ );
8192
8334
  }
8193
8335
  }
8194
8336
  validateStaticAuctionParams(params) {
@@ -8229,7 +8371,6 @@ var DopplerFactory = class {
8229
8371
  throw new Error("Cannot sell more tokens than initial supply");
8230
8372
  }
8231
8373
  this.validateVestingConfig(params.sale, params.vesting);
8232
- this.validateVestingConfig(params.sale, params.vesting);
8233
8374
  if (params.migration.type === "dopplerHook") {
8234
8375
  throw new Error(
8235
8376
  "dopplerHook migration is only supported for dynamic auctions"
@@ -13424,6 +13565,35 @@ var Eth = class {
13424
13565
  };
13425
13566
 
13426
13567
  // src/evm/builders/shared.ts
13568
+ var MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
13569
+ function normalizeBuilderVestingScheduleDuration(value, fieldPath) {
13570
+ const duration = value ?? 0n;
13571
+ if (duration < 0n) {
13572
+ throw new RangeError(`${fieldPath} cannot be negative`);
13573
+ }
13574
+ if (duration > MAX_SAFE_INTEGER_BIGINT) {
13575
+ throw new RangeError(`${fieldPath} must be a safe integer`);
13576
+ }
13577
+ return Number(duration);
13578
+ }
13579
+ function normalizeBuilderScheduleId(scheduleId, fieldPath) {
13580
+ if (typeof scheduleId === "bigint") {
13581
+ if (scheduleId < 0n) {
13582
+ throw new RangeError(`${fieldPath} cannot be negative`);
13583
+ }
13584
+ if (scheduleId > MAX_SAFE_INTEGER_BIGINT) {
13585
+ throw new RangeError(`${fieldPath} must be a safe integer`);
13586
+ }
13587
+ return Number(scheduleId);
13588
+ }
13589
+ if (!Number.isSafeInteger(scheduleId)) {
13590
+ throw new RangeError(`${fieldPath} must be a safe integer`);
13591
+ }
13592
+ if (scheduleId < 0) {
13593
+ throw new RangeError(`${fieldPath} cannot be negative`);
13594
+ }
13595
+ return scheduleId;
13596
+ }
13427
13597
  function computeTicks(priceRange, tickSpacing) {
13428
13598
  const startTick = Math.floor(
13429
13599
  Math.log(priceRange.startPrice) / Math.log(1.0001) / tickSpacing
@@ -13719,11 +13889,24 @@ var StaticAuctionBuilder = class _StaticAuctionBuilder {
13719
13889
  this.vesting = void 0;
13720
13890
  return this;
13721
13891
  }
13892
+ const hasCustomSchedules = (params.schedules?.length ?? 0) > 0 || (params.scheduleIds?.length ?? 0) > 0;
13722
13893
  this.vesting = {
13723
- duration: Number(params.duration ?? DEFAULT_V3_VESTING_DURATION),
13894
+ duration: Number(
13895
+ params.duration ?? (hasCustomSchedules ? 0n : DEFAULT_V3_VESTING_DURATION)
13896
+ ),
13724
13897
  cliffDuration: params.cliffDuration ?? 0,
13725
13898
  recipients: params.recipients,
13726
- amounts: params.amounts
13899
+ amounts: params.amounts,
13900
+ schedules: params.schedules?.map((schedule) => ({
13901
+ duration: normalizeBuilderVestingScheduleDuration(
13902
+ schedule.duration,
13903
+ "Vesting schedule duration"
13904
+ ),
13905
+ cliffDuration: schedule.cliffDuration ?? 0
13906
+ })),
13907
+ scheduleIds: params.scheduleIds?.map(
13908
+ (scheduleId, index) => normalizeBuilderScheduleId(scheduleId, `Vesting scheduleIds[${index}]`)
13909
+ )
13727
13910
  };
13728
13911
  return this;
13729
13912
  }
@@ -14055,7 +14238,17 @@ var DynamicAuctionBuilder = class _DynamicAuctionBuilder {
14055
14238
  duration: Number(params.duration ?? 0n),
14056
14239
  cliffDuration: params.cliffDuration ?? 0,
14057
14240
  recipients: params.recipients,
14058
- amounts: params.amounts
14241
+ amounts: params.amounts,
14242
+ schedules: params.schedules?.map((schedule) => ({
14243
+ duration: normalizeBuilderVestingScheduleDuration(
14244
+ schedule.duration,
14245
+ "Vesting schedule duration"
14246
+ ),
14247
+ cliffDuration: schedule.cliffDuration ?? 0
14248
+ })),
14249
+ scheduleIds: params.scheduleIds?.map(
14250
+ (scheduleId, index) => normalizeBuilderScheduleId(scheduleId, `Vesting scheduleIds[${index}]`)
14251
+ )
14059
14252
  };
14060
14253
  return this;
14061
14254
  }
@@ -14661,7 +14854,17 @@ var MulticurveBuilder = class _MulticurveBuilder {
14661
14854
  duration: Number(params.duration ?? 0n),
14662
14855
  cliffDuration: params.cliffDuration ?? 0,
14663
14856
  recipients: params.recipients,
14664
- amounts: params.amounts
14857
+ amounts: params.amounts,
14858
+ schedules: params.schedules?.map((schedule) => ({
14859
+ duration: normalizeBuilderVestingScheduleDuration(
14860
+ schedule.duration,
14861
+ "Vesting schedule duration"
14862
+ ),
14863
+ cliffDuration: schedule.cliffDuration ?? 0
14864
+ })),
14865
+ scheduleIds: params.scheduleIds?.map(
14866
+ (scheduleId, index) => normalizeBuilderScheduleId(scheduleId, `Vesting scheduleIds[${index}]`)
14867
+ )
14665
14868
  };
14666
14869
  return this;
14667
14870
  }
@@ -15050,7 +15253,17 @@ var OpeningAuctionBuilder = class _OpeningAuctionBuilder {
15050
15253
  duration: Number(params.duration ?? 0n),
15051
15254
  cliffDuration: params.cliffDuration ?? 0,
15052
15255
  recipients: params.recipients,
15053
- amounts: params.amounts
15256
+ amounts: params.amounts,
15257
+ schedules: params.schedules?.map((schedule) => ({
15258
+ duration: normalizeBuilderVestingScheduleDuration(
15259
+ schedule.duration,
15260
+ "Vesting schedule duration"
15261
+ ),
15262
+ cliffDuration: schedule.cliffDuration ?? 0
15263
+ })),
15264
+ scheduleIds: params.scheduleIds?.map(
15265
+ (scheduleId, index) => normalizeBuilderScheduleId(scheduleId, `Vesting scheduleIds[${index}]`)
15266
+ )
15054
15267
  };
15055
15268
  return this;
15056
15269
  }