@whetstone-research/doppler-sdk 1.0.9 → 1.0.10
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 +170 -29
- package/dist/evm/index.cjs +6411 -3526
- package/dist/evm/index.cjs.map +1 -1
- package/dist/evm/index.d.cts +1512 -78
- package/dist/evm/index.d.ts +1512 -78
- package/dist/evm/index.js +6408 -3527
- package/dist/evm/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The Doppler SDK consolidates functionality from the previous `doppler-v3-sdk` an
|
|
|
11
11
|
- **Static Auctions**: Fixed price range liquidity bootstrapping using Uniswap V3
|
|
12
12
|
- **Dynamic Auctions**: Gradual Dutch auctions using Uniswap V4 hooks
|
|
13
13
|
- **Multicurve Initializer**: Seed Uniswap V4 pools across multiple curves
|
|
14
|
-
- **Flexible Migration**: Support for
|
|
14
|
+
- **Flexible Migration**: Support for V2, V2 split, V4, V4 split, DopplerHook, and no-op migration paths
|
|
15
15
|
- **Token Management**: Built-in support for DERC20 tokens with vesting
|
|
16
16
|
- **Type Safety**: Full TypeScript support with discriminated unions
|
|
17
17
|
- **Chain Support**: Works with Base, Unichain, Ink, and other EVM chains
|
|
@@ -65,8 +65,9 @@ Static auctions use Uniswap V3 pools with concentrated liquidity in a fixed pric
|
|
|
65
65
|
|
|
66
66
|
```typescript
|
|
67
67
|
import { StaticAuctionBuilder } from '@whetstone-research/doppler-sdk/evm';
|
|
68
|
+
import { base } from 'viem/chains';
|
|
68
69
|
|
|
69
|
-
const params = new StaticAuctionBuilder()
|
|
70
|
+
const params = new StaticAuctionBuilder(base.id)
|
|
70
71
|
.tokenConfig({
|
|
71
72
|
name: 'My Token',
|
|
72
73
|
symbol: 'MTK',
|
|
@@ -186,8 +187,9 @@ import {
|
|
|
186
187
|
DynamicAuctionBuilder,
|
|
187
188
|
DAY_SECONDS,
|
|
188
189
|
} from '@whetstone-research/doppler-sdk/evm';
|
|
190
|
+
import { base } from 'viem/chains';
|
|
189
191
|
|
|
190
|
-
const params = new DynamicAuctionBuilder()
|
|
192
|
+
const params = new DynamicAuctionBuilder(base.id)
|
|
191
193
|
.tokenConfig({
|
|
192
194
|
name: 'My Token',
|
|
193
195
|
symbol: 'MTK',
|
|
@@ -232,7 +234,7 @@ const params = new DynamicAuctionBuilder()
|
|
|
232
234
|
.withDopplerDeployer('0xDeployer...')
|
|
233
235
|
.withTokenFactory('0xFactory...')
|
|
234
236
|
.withV4Initializer('0xInitializer...')
|
|
235
|
-
.withGovernanceFactory('0xGovFactory...') // used for
|
|
237
|
+
.withGovernanceFactory('0xGovFactory...') // used for standard, no-op, or launchpad governance overrides
|
|
236
238
|
// .withV2Migrator('0xV2Migrator...')
|
|
237
239
|
// .withV3Migrator('0xV3Migrator...')
|
|
238
240
|
// .withV4Migrator('0xV4Migrator...')
|
|
@@ -354,7 +356,7 @@ const params = new MulticurveBuilder(base.id)
|
|
|
354
356
|
],
|
|
355
357
|
})
|
|
356
358
|
.withGovernance({ type: 'default' })
|
|
357
|
-
// Choose a migration path (V2,
|
|
359
|
+
// Choose a migration path (V2, V2 split, V4, V4 split, DopplerHook, or noOp)
|
|
358
360
|
.withMigration({ type: 'uniswapV2' })
|
|
359
361
|
.withUserAddress('0x...')
|
|
360
362
|
.build();
|
|
@@ -598,9 +600,10 @@ import {
|
|
|
598
600
|
DynamicAuctionBuilder,
|
|
599
601
|
} from '@whetstone-research/doppler-sdk/evm';
|
|
600
602
|
import { parseEther } from 'viem';
|
|
603
|
+
import { base } from 'viem/chains';
|
|
601
604
|
|
|
602
605
|
// Dynamic auction via builder
|
|
603
|
-
const dynamicParams = new DynamicAuctionBuilder()
|
|
606
|
+
const dynamicParams = new DynamicAuctionBuilder(base.id)
|
|
604
607
|
.tokenConfig({
|
|
605
608
|
name: 'My Token',
|
|
606
609
|
symbol: 'MTK',
|
|
@@ -624,7 +627,7 @@ const dynamicParams = new DynamicAuctionBuilder()
|
|
|
624
627
|
const dyn = await sdk.factory.createDynamicAuction(dynamicParams);
|
|
625
628
|
|
|
626
629
|
// Static auction via builder
|
|
627
|
-
const staticParams = new StaticAuctionBuilder()
|
|
630
|
+
const staticParams = new StaticAuctionBuilder(base.id)
|
|
628
631
|
.tokenConfig({
|
|
629
632
|
name: 'My Token',
|
|
630
633
|
symbol: 'MTK',
|
|
@@ -652,7 +655,7 @@ The SDK intelligently applies defaults when parameters are omitted. Here are exa
|
|
|
652
655
|
|
|
653
656
|
```typescript
|
|
654
657
|
// Minimal static auction via builder
|
|
655
|
-
const staticMinimal = new StaticAuctionBuilder()
|
|
658
|
+
const staticMinimal = new StaticAuctionBuilder(base.id)
|
|
656
659
|
.tokenConfig({
|
|
657
660
|
name: 'My Token',
|
|
658
661
|
symbol: 'MTK',
|
|
@@ -671,7 +674,7 @@ const staticMinimal = new StaticAuctionBuilder()
|
|
|
671
674
|
const staticResult = await sdk.factory.createStaticAuction(staticMinimal);
|
|
672
675
|
|
|
673
676
|
// Minimal dynamic auction via builder
|
|
674
|
-
const dynamicMinimal = new DynamicAuctionBuilder()
|
|
677
|
+
const dynamicMinimal = new DynamicAuctionBuilder(base.id)
|
|
675
678
|
.tokenConfig({
|
|
676
679
|
name: 'My Token',
|
|
677
680
|
symbol: 'MTK',
|
|
@@ -832,6 +835,67 @@ import { Derc20 } from '@whetstone-research/doppler-sdk/evm';
|
|
|
832
835
|
const tokenDirect = new Derc20(publicClient, walletClient, tokenAddress);
|
|
833
836
|
```
|
|
834
837
|
|
|
838
|
+
### DopplerERC20V1 Tokens
|
|
839
|
+
|
|
840
|
+
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.
|
|
841
|
+
|
|
842
|
+
When balance limiting is enabled on the default DopplerERC20V1 integration, the SDK encodes user exclusions plus determinable protocol recipients for the selected auction path into deployment-time `excludedFromBalanceLimit`, including initializers, hooks, PoolManager, migrators, known migration pools, no-op governance, and launchpad governance multisigs. Custom `withTokenFactory(address)` paths receive only the `excludedFromBalanceLimit` entries supplied in `tokenConfig`, so custom factory users must provide any required deployment-time exclusions themselves. Exclusions cannot be added later through the controller or governance. Default and custom governance timelocks are not precomputed before create, so account for them before deployment or disable strict limits early via the controller when needed.
|
|
843
|
+
|
|
844
|
+
DopplerERC20V1 supports vesting through `withVesting` while staying on the DopplerERC20V1 factory path: use `duration` with optional `cliffDuration` for a shared schedule, or `allocations` for per-beneficiary schedules.
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
const params = new StaticAuctionBuilder(base.id)
|
|
848
|
+
.tokenConfig({
|
|
849
|
+
name: 'My Doppler Token',
|
|
850
|
+
symbol: 'MDT',
|
|
851
|
+
tokenURI: 'ipfs://doppler-token.json',
|
|
852
|
+
maxBalanceLimit: parseEther('10000'),
|
|
853
|
+
balanceLimitEnd: Math.floor(Date.now() / 1000) + 30 * DAY_SECONDS,
|
|
854
|
+
controller: userAddress, // optional; defaults to zero address when omitted
|
|
855
|
+
excludedFromBalanceLimit: [userAddress], // default DopplerERC20V1 path also adds protocol modules
|
|
856
|
+
})
|
|
857
|
+
.saleConfig({
|
|
858
|
+
initialSupply: parseEther('1000000'),
|
|
859
|
+
numTokensToSell: parseEther('900000'),
|
|
860
|
+
numeraire: wethAddress,
|
|
861
|
+
})
|
|
862
|
+
.poolByTicks({ startTick: -120000, endTick: -60000, fee: 3000 })
|
|
863
|
+
.withVesting({
|
|
864
|
+
duration: 365n * BigInt(DAY_SECONDS),
|
|
865
|
+
cliffDuration: 30 * DAY_SECONDS,
|
|
866
|
+
recipients: [userAddress],
|
|
867
|
+
amounts: [parseEther('100000')],
|
|
868
|
+
})
|
|
869
|
+
.withMigration({ type: 'uniswapV2' })
|
|
870
|
+
.withUserAddress(userAddress)
|
|
871
|
+
.build();
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
DopplerERC20V1 token data includes schedule vesting and balance-limit controls, but it intentionally omits `yearlyMintRate`; DopplerERC20V1 tokens do not expose `mintInflation` or mint-rate update helpers.
|
|
875
|
+
|
|
876
|
+
```typescript
|
|
877
|
+
const token = sdk.getDopplerERC20V1(tokenAddress);
|
|
878
|
+
const scheduleCount = await token.getVestingScheduleCount();
|
|
879
|
+
|
|
880
|
+
for (let scheduleId = 0n; scheduleId < scheduleCount; scheduleId++) {
|
|
881
|
+
const schedule = await token.getVestingSchedule(scheduleId);
|
|
882
|
+
const available = await token.getAvailableVestedAmountForSchedule(
|
|
883
|
+
userAddress,
|
|
884
|
+
scheduleId,
|
|
885
|
+
);
|
|
886
|
+
console.log(schedule, available);
|
|
887
|
+
|
|
888
|
+
// Release half of the available vested amount for one schedule.
|
|
889
|
+
if (available > 0n) await token.releaseSchedule(scheduleId, available / 2n);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
console.log(await token.getMaxBalanceLimit());
|
|
893
|
+
console.log(await token.getBalanceLimitEnd());
|
|
894
|
+
console.log(await token.isBalanceLimitActive());
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
For a runnable example, see [examples/doppler-erc20-v1.ts](./examples/doppler-erc20-v1.ts).
|
|
898
|
+
|
|
835
899
|
### Governance Delegation (ERC20Votes)
|
|
836
900
|
|
|
837
901
|
DERC20 extends OpenZeppelin's ERC20Votes. Voting power is tracked via checkpoints and only updates once an address delegates voting power (typically to itself). The SDK exposes simple read/write helpers for delegation.
|
|
@@ -1031,32 +1095,100 @@ migration: {
|
|
|
1031
1095
|
}
|
|
1032
1096
|
```
|
|
1033
1097
|
|
|
1034
|
-
### Migrate to Uniswap
|
|
1098
|
+
### Migrate to Uniswap V4
|
|
1035
1099
|
|
|
1036
1100
|
```typescript
|
|
1037
1101
|
migration: {
|
|
1038
|
-
type: '
|
|
1039
|
-
fee: 3000,
|
|
1040
|
-
tickSpacing: 60,
|
|
1102
|
+
type: 'uniswapV4',
|
|
1103
|
+
fee: 3000,
|
|
1104
|
+
tickSpacing: 60,
|
|
1105
|
+
streamableFees: {
|
|
1106
|
+
lockDuration: 365 * 24 * 60 * 60, // 1 year
|
|
1107
|
+
beneficiaries: [
|
|
1108
|
+
{ beneficiary: '0x...', shares: parseEther('1') }, // 100%
|
|
1109
|
+
],
|
|
1110
|
+
},
|
|
1041
1111
|
}
|
|
1042
1112
|
```
|
|
1043
1113
|
|
|
1044
|
-
### Migrate to Uniswap
|
|
1114
|
+
### Migrate to Uniswap V2 with Proceeds Split + Top-ups
|
|
1045
1115
|
|
|
1046
1116
|
```typescript
|
|
1047
1117
|
migration: {
|
|
1048
|
-
type: '
|
|
1118
|
+
type: 'uniswapV2Split',
|
|
1119
|
+
proceedsSplit: {
|
|
1120
|
+
recipient: '0xRecipient...',
|
|
1121
|
+
share: parseEther('0.1'), // 10%, capped at 50%
|
|
1122
|
+
},
|
|
1123
|
+
}
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
- The split recipient receives the configured share of numeraire proceeds during migration.
|
|
1127
|
+
- If the asset/numeraire pair was topped up in `TopUpDistributor` before migration, the split recipient also receives those top-ups automatically.
|
|
1128
|
+
|
|
1129
|
+
### Migrate to Uniswap V4 with Proceeds Split + Top-ups
|
|
1130
|
+
|
|
1131
|
+
```typescript
|
|
1132
|
+
migration: {
|
|
1133
|
+
type: 'uniswapV4Split',
|
|
1049
1134
|
fee: 3000,
|
|
1050
|
-
tickSpacing:
|
|
1135
|
+
tickSpacing: 8,
|
|
1051
1136
|
streamableFees: {
|
|
1052
|
-
lockDuration:
|
|
1137
|
+
lockDuration: 30 * 24 * 60 * 60,
|
|
1053
1138
|
beneficiaries: [
|
|
1054
|
-
{ beneficiary: '
|
|
1139
|
+
{ beneficiary: '0xAirlockOwner...', shares: parseEther('0.05') },
|
|
1140
|
+
{ beneficiary: '0xTeam...', shares: parseEther('0.95') },
|
|
1055
1141
|
],
|
|
1056
1142
|
},
|
|
1143
|
+
proceedsSplit: {
|
|
1144
|
+
recipient: '0xRecipient...',
|
|
1145
|
+
share: parseEther('0.1'),
|
|
1146
|
+
},
|
|
1057
1147
|
}
|
|
1058
1148
|
```
|
|
1059
1149
|
|
|
1150
|
+
- `streamableFees` is required for `uniswapV4Split`.
|
|
1151
|
+
- Beneficiaries must sum to `1e18`, and the Airlock owner must be included with at least 5% shares.
|
|
1152
|
+
- The split recipient also receives any `TopUpDistributor` funds pulled during migration.
|
|
1153
|
+
|
|
1154
|
+
### TopUpDistributor Top-ups
|
|
1155
|
+
|
|
1156
|
+
The SDK exposes `sdk.topUpDistributor` and `sdk.getTopUpDistributor(address?)`
|
|
1157
|
+
for building, simulating, and submitting `topUp({ asset, numeraire, amount })`
|
|
1158
|
+
transactions where `getAddresses(chainId).topUpDistributor` is configured. The helper methods
|
|
1159
|
+
accept the same object shape for `buildTopUpTransaction({ asset, numeraire, amount })` and
|
|
1160
|
+
`simulateTopUp({ asset, numeraire, amount })`. ETH top-ups use `numeraire = ZERO_ADDRESS` and
|
|
1161
|
+
send `value = amount`; ERC20 top-ups send no native value and require the user to approve the
|
|
1162
|
+
`TopUpDistributor` before calling `topUp`.
|
|
1163
|
+
|
|
1164
|
+
```typescript
|
|
1165
|
+
import { ZERO_ADDRESS } from '@whetstone-research/doppler-sdk/evm';
|
|
1166
|
+
import { parseEther } from 'viem';
|
|
1167
|
+
|
|
1168
|
+
const topUps = sdk.topUpDistributor;
|
|
1169
|
+
|
|
1170
|
+
const tx = topUps.buildTopUpTransaction({
|
|
1171
|
+
asset: tokenAddress,
|
|
1172
|
+
numeraire: ZERO_ADDRESS,
|
|
1173
|
+
amount: parseEther('1'),
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
const simulation = await topUps.simulateTopUp({
|
|
1177
|
+
asset: tokenAddress,
|
|
1178
|
+
numeraire: ZERO_ADDRESS,
|
|
1179
|
+
amount: parseEther('1'),
|
|
1180
|
+
});
|
|
1181
|
+
|
|
1182
|
+
await topUps.topUp({
|
|
1183
|
+
asset: tokenAddress,
|
|
1184
|
+
numeraire: ZERO_ADDRESS,
|
|
1185
|
+
amount: parseEther('1'),
|
|
1186
|
+
});
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
Split migrators pull any TopUpDistributor balance for the asset/numeraire pair during migration
|
|
1190
|
+
and pay it to the configured split recipient.
|
|
1191
|
+
|
|
1060
1192
|
### Migrate via DopplerHookMigrator (Dynamic Auctions)
|
|
1061
1193
|
|
|
1062
1194
|
Use this mode when you want rehypothecation / custom hook behavior on the
|
|
@@ -1266,7 +1398,7 @@ const builder = new StaticAuctionBuilder(base.id)
|
|
|
1266
1398
|
})
|
|
1267
1399
|
.poolByTicks({ startTick: -92100, endTick: -69060, fee: 3000 })
|
|
1268
1400
|
.withGovernance({ type: 'default' })
|
|
1269
|
-
.withMigration({ type: '
|
|
1401
|
+
.withMigration({ type: 'uniswapV4', fee: 3000, tickSpacing: 60 })
|
|
1270
1402
|
.withUserAddress('0x...');
|
|
1271
1403
|
|
|
1272
1404
|
const staticParams = builder.build();
|
|
@@ -1321,7 +1453,7 @@ import {
|
|
|
1321
1453
|
import { parseEther, keccak256, encodePacked, encodeAbiParameters } from 'viem';
|
|
1322
1454
|
import { base } from 'viem/chains';
|
|
1323
1455
|
|
|
1324
|
-
const builder = new DynamicAuctionBuilder()
|
|
1456
|
+
const builder = new DynamicAuctionBuilder(base.id)
|
|
1325
1457
|
.tokenConfig({
|
|
1326
1458
|
name: 'My Token',
|
|
1327
1459
|
symbol: 'MTK',
|
|
@@ -1499,7 +1631,7 @@ Note: Dual-prefix mining takes significantly longer than single-prefix mining. C
|
|
|
1499
1631
|
- **Prefix format**: Omit the `0x` prefix (e.g., use `'dead'` not `'0x dead'`)
|
|
1500
1632
|
- **Case insensitive**: `'DEAD'`, `'dead'`, and `'DeAd'` are equivalent
|
|
1501
1633
|
- **Iteration limit**: Longer prefixes require more iterations. A 4-character hex prefix takes ~65,000 attempts on average.
|
|
1502
|
-
- **Token variants**: Set `tokenVariant: 'doppler404'` for DN404-style tokens
|
|
1634
|
+
- **Token variants**: Set `tokenVariant: 'standard-v2'` or `tokenVariant: 'dopplerERC20V1'` with `v2Implementation` for clone templates, or `tokenVariant: 'doppler404'` for DN404-style tokens
|
|
1503
1635
|
- **Salt preservation**: High-level helpers like `createStaticAuction` and `createDynamicAuction` recompute salts internally to ensure proper token ordering. To use a mined salt, call `encodeCreate*Params` and submit the transaction manually via `publicClient.writeContract`
|
|
1504
1636
|
- **Hook flags**: The miner automatically ensures V4 hooks have the correct permission flags for Doppler operations
|
|
1505
1637
|
|
|
@@ -1558,7 +1690,7 @@ pnpm build
|
|
|
1558
1690
|
pnpm test
|
|
1559
1691
|
|
|
1560
1692
|
# Run specific test suite
|
|
1561
|
-
pnpm test
|
|
1693
|
+
pnpm test:whitelisting
|
|
1562
1694
|
|
|
1563
1695
|
# Run tests in watch mode
|
|
1564
1696
|
pnpm test:watch
|
|
@@ -1571,22 +1703,31 @@ pnpm dev
|
|
|
1571
1703
|
|
|
1572
1704
|
The SDK includes comprehensive tests covering:
|
|
1573
1705
|
|
|
1574
|
-
- **Airlock Whitelisting**: Verifies that all modules are properly whitelisted on
|
|
1706
|
+
- **Airlock Whitelisting**: Verifies that all modules are properly whitelisted on Ethereum Mainnet, Monad Mainnet, Base Mainnet, and Base Sepolia
|
|
1575
1707
|
- **Multicurve Functionality**: Tests multicurve auction creation and quoting
|
|
1576
1708
|
- **Token Address Mining**: Tests for generating optimized token addresses
|
|
1577
1709
|
|
|
1578
|
-
See [`test/README.md`](./test/README.md) for detailed testing documentation.
|
|
1579
|
-
|
|
1580
1710
|
To run whitelisting tests:
|
|
1581
1711
|
|
|
1582
1712
|
```bash
|
|
1583
|
-
#
|
|
1584
|
-
pnpm test
|
|
1713
|
+
# Canonical whitelist audit
|
|
1714
|
+
pnpm test:whitelisting
|
|
1585
1715
|
|
|
1586
|
-
#
|
|
1587
|
-
ALCHEMY_API_KEY=your_key_here pnpm test
|
|
1716
|
+
# With Alchemy fallback (faster and more reliable)
|
|
1717
|
+
ALCHEMY_API_KEY=your_key_here pnpm test:whitelisting
|
|
1718
|
+
|
|
1719
|
+
# Limit to specific whitelist-audit chains when needed
|
|
1720
|
+
TEST_CHAINS=mainnet,base,base-sepolia,monad-mainnet pnpm test:whitelisting
|
|
1588
1721
|
```
|
|
1589
1722
|
|
|
1723
|
+
The whitelisting suite is scoped to the release-audit chains: Ethereum Mainnet, Monad Mainnet, Base Mainnet, and Base Sepolia.
|
|
1724
|
+
|
|
1725
|
+
Whitelisting test RPC priority is:
|
|
1726
|
+
|
|
1727
|
+
1. Chain-specific RPC URL env var (`ETH_MAINNET_RPC_URL`, `BASE_RPC_URL`, `BASE_SEPOLIA_RPC_URL`, `MONAD_MAINNET_RPC_URL`)
|
|
1728
|
+
2. `ALCHEMY_API_KEY` fallback for supported Alchemy networks, including Monad Mainnet
|
|
1729
|
+
3. Public/default RPC URL
|
|
1730
|
+
|
|
1590
1731
|
To run fork tests (Anvil):
|
|
1591
1732
|
|
|
1592
1733
|
```bash
|