doppler-v3-sdk
Version:
SDK for interacting with Doppler v3 protocol
305 lines • 12.3 kB
JavaScript
import { createDrift, } from "@delvtech/drift";
import { ReadFactory } from "./ReadFactory";
import { BundlerAbi } from "../../abis";
import { encodeAbiParameters, parseEther, toHex } from "viem";
// Constants for default configuration values
export const ONE_YEAR_IN_SECONDS = 365 * 24 * 60 * 60;
export const DEFAULT_START_TICK = 175000;
export const DEFAULT_END_TICK = 225000;
export const DEFAULT_NUM_POSITIONS = 15;
export const DEFAULT_FEE = 10000; // 1% fee tier
export const DEFAULT_VESTING_DURATION = BigInt(ONE_YEAR_IN_SECONDS);
export const DEFAULT_INITIAL_SUPPLY_WAD = parseEther("1000000000");
export const DEFAULT_NUM_TOKENS_TO_SELL_WAD = parseEther("900000000");
export const DEFAULT_YEARLY_MINT_RATE_WAD = parseEther("0.02");
export const DEFAULT_PRE_MINT_WAD = parseEther("9000000"); // 0.9% of the total supply
export const DEFAULT_MAX_SHARE_TO_BE_SOLD = parseEther("0.35");
export const DEFAULT_INITIAL_VOTING_DELAY = 172800;
export const DEFAULT_INITIAL_VOTING_PERIOD = 1209600;
export const DEFAULT_INITIAL_PROPOSAL_THRESHOLD = BigInt(0);
/**
* Factory class for creating and managing Doppler V3 pools with read/write capabilities
*/
export class ReadWriteFactory extends ReadFactory {
/**
* Create a new ReadWriteFactory instance
* @param address Contract address
* @param drift Drift instance for blockchain interaction
* @param defaultConfigs Optional default configurations
*/
constructor(address, bundlerAddress, drift = createDrift(), defaultConfigs) {
super(address, drift);
/**
* Generate a random salt
* @param account User address to incorporate into salt
* @returns Hex string of generated salt
*/
this.generateRandomSalt = (account) => {
const array = new Uint8Array(32);
// Sequential byte generation
for (let i = 0; i < 32; i++) {
array[i] = i;
}
if (account) {
const addressBytes = account.slice(2).padStart(40, "0");
for (let i = 0; i < 20; i++) {
const addressByte = parseInt(addressBytes.slice(i * 2, (i + 1) * 2), 16);
array[i] ^= addressByte;
}
}
return `0x${Array.from(array)
.map((b) => b.toString(16).padStart(2, "0"))
.join("")}`;
};
this.bundler = drift.contract({
abi: BundlerAbi,
address: bundlerAddress,
});
// Initialize default configurations with fallback values
this.defaultV3PoolConfig = defaultConfigs?.defaultV3PoolConfig ?? {
startTick: DEFAULT_START_TICK,
endTick: DEFAULT_END_TICK,
numPositions: DEFAULT_NUM_POSITIONS,
maxShareToBeSold: DEFAULT_MAX_SHARE_TO_BE_SOLD,
fee: DEFAULT_FEE,
};
this.defaultVestingConfig = defaultConfigs?.defaultVestingConfig ?? {
yearlyMintRate: DEFAULT_YEARLY_MINT_RATE_WAD,
vestingDuration: DEFAULT_VESTING_DURATION,
recipients: [],
amounts: [],
};
this.defaultSaleConfig = defaultConfigs?.defaultSaleConfig ?? {
initialSupply: DEFAULT_INITIAL_SUPPLY_WAD,
numTokensToSell: DEFAULT_NUM_TOKENS_TO_SELL_WAD,
};
this.defaultGovernanceConfig = defaultConfigs?.defaultGovernanceConfig ?? {
initialVotingDelay: DEFAULT_INITIAL_VOTING_DELAY,
initialVotingPeriod: DEFAULT_INITIAL_VOTING_PERIOD,
initialProposalThreshold: DEFAULT_INITIAL_PROPOSAL_THRESHOLD,
};
}
/**
* Merge user configuration with defaults
* @param config User-provided partial configuration
* @param defaults Full default configuration
* @returns Merged configuration object
*/
mergeWithDefaults(config, defaults) {
return { ...defaults, ...config };
}
/**
* Get merged sale configuration
* @param saleConfig Optional partial sale config
* @returns Complete SaleConfig
*/
getMergedSaleConfig(saleConfig) {
return this.mergeWithDefaults(saleConfig, this.defaultSaleConfig);
}
/**
* Get merged pool configuration
* @param v3PoolConfig Optional partial pool config
* @returns Complete V3PoolConfig
*/
getMergedV3PoolConfig(v3PoolConfig) {
return this.mergeWithDefaults(v3PoolConfig, this.defaultV3PoolConfig);
}
/**
* Get merged governance configuration
* @param governanceConfig Optional partial governance config
* @returns Complete GovernanceConfig
*/
getMergedGovernanceConfig(governanceConfig) {
return this.mergeWithDefaults(governanceConfig, this.defaultGovernanceConfig);
}
/**
* Get merged vesting configuration
* @param config Vesting config or "default" preset
* @param userAddress User address for default recipient
* @returns Complete VestingConfig
*/
getMergedVestingConfig(config, userAddress) {
const base = config === "default" ? this.defaultVestingConfig : config;
return {
...base,
recipients: config === "default" ? [userAddress] : [...base.recipients],
amounts: config === "default" ? [DEFAULT_PRE_MINT_WAD] : [...base.amounts],
};
}
/**
* Encode pool initialization data for contract calls
* @param v3PoolConfig Complete pool configuration
* @returns ABI-encoded initialization data
*/
encodePoolInitializerData(v3PoolConfig) {
return encodeAbiParameters([
{ type: "uint24" },
{ type: "int24" },
{ type: "int24" },
{ type: "uint16" },
{ type: "uint256" },
], [
v3PoolConfig.fee,
v3PoolConfig.startTick,
v3PoolConfig.endTick,
v3PoolConfig.numPositions,
v3PoolConfig.maxShareToBeSold,
]);
}
/**
* Encode token factory initialization data
* @param tokenConfig Token metadata
* @param vestingConfig Vesting schedule
* @returns ABI-encoded token factory data
*/
encodeTokenFactoryData(tokenConfig, vestingConfig) {
return encodeAbiParameters([
{ type: "string" },
{ type: "string" },
{ type: "uint256" },
{ type: "uint256" },
{ type: "address[]" },
{ type: "uint256[]" },
{ type: "string" },
], [
tokenConfig.name,
tokenConfig.symbol,
vestingConfig.yearlyMintRate,
vestingConfig.vestingDuration,
vestingConfig.recipients,
vestingConfig.amounts,
tokenConfig.tokenURI,
]);
}
/**
* Encode governance factory initialization data
* @param tokenConfig Token metadata
* @returns ABI-encoded governance data
*/
encodeGovernanceFactoryData(tokenConfig, governanceConfig) {
return encodeAbiParameters([
{ type: "string" },
{ type: "uint48" },
{ type: "uint32" },
{ type: "uint256" },
], [
tokenConfig.name,
Number(governanceConfig.initialVotingDelay),
Number(governanceConfig.initialVotingPeriod),
governanceConfig.initialProposalThreshold,
]);
}
/**
* Encode all parameters for pool creation
* @param params CreateV3PoolParams input parameters
* @returns Object containing create parameters and final pool config
* @throws Error if user address is missing or invalid tick range
*/
encode(params) {
const { userAddress, numeraire, integrator, contracts, tokenConfig } = params;
if (!userAddress) {
throw new Error("User address is required. Is a wallet connected?");
}
// Merge configurations with defaults
const vestingConfig = this.getMergedVestingConfig(params.vestingConfig, userAddress);
const v3PoolConfig = this.getMergedV3PoolConfig(params.v3PoolConfig);
const saleConfig = this.getMergedSaleConfig(params.saleConfig);
const governanceConfig = this.getMergedGovernanceConfig(params.governanceConfig);
// Validate tick configuration
if (v3PoolConfig.startTick > v3PoolConfig.endTick) {
throw new Error("Invalid start and end ticks. Start tick must be less than end tick.");
}
// Generate unique salt and encode contract data
const salt = this.generateRandomSalt(userAddress);
const governanceFactoryData = this.encodeGovernanceFactoryData(tokenConfig, governanceConfig);
const tokenFactoryData = this.encodeTokenFactoryData(tokenConfig, vestingConfig);
const poolInitializerData = this.encodePoolInitializerData(v3PoolConfig);
const liquidityMigratorData = "0x";
// Prepare final arguments
const { tokenFactory, governanceFactory, v3Initializer: poolInitializer, liquidityMigrator, } = contracts;
const { initialSupply, numTokensToSell } = saleConfig;
const args = {
initialSupply,
numTokensToSell,
numeraire,
tokenFactory,
tokenFactoryData,
governanceFactory,
governanceFactoryData,
poolInitializer,
poolInitializerData,
liquidityMigrator,
liquidityMigratorData,
integrator,
salt,
};
return {
createParams: args,
v3PoolConfig,
};
}
/**
* Encode creation data with token order validation
* @param params CreateV3PoolParams input parameters
* @returns Finalized create parameters with adjusted ticks if needed
*/
async encodeCreateData(params) {
let isToken0 = true;
let createParams;
let asset;
let i = 0n;
while (isToken0) {
const encoded = this.encode(params);
createParams = encoded.createParams;
createParams.salt = this.generateRandomSalt(toHex(BigInt(params.userAddress) + BigInt(i)));
const simulateResult = await this.simulateCreate(createParams);
asset = simulateResult.asset;
isToken0 = Number(asset) < Number(params.numeraire);
i++;
}
return createParams;
}
/**
* Execute pool creation transaction
* @param params Finalized create parameters
* @param options Write options and mined handlers
* @returns Transaction hash
*/
async create(params, options) {
return this.airlock.write("create", { createData: params }, options);
}
/**
* Simulate pool creation transaction
* @param params Create parameters
* @returns Simulation results
*/
async simulateCreate(params) {
return this.airlock.simulateWrite("create", { createData: params });
}
async simulateBundleExactOutput(createData, params) {
return this.bundler.simulateWrite("simulateBundleExactOut", {
createData,
params: { ...params },
});
}
async simulateBundleExactInput(createData, params) {
return this.bundler.simulateWrite("simulateBundleExactIn", {
createData,
params: { ...params },
});
}
async bundle(createData, commands, inputs, options) {
return this.bundler.write("bundle", { createData, commands, inputs }, options);
}
/**
* Update default configurations
* @param configs Partial configuration overrides
*/
updateDefaultConfigs(configs) {
this.defaultV3PoolConfig = this.mergeWithDefaults(configs.defaultV3PoolConfig || {}, this.defaultV3PoolConfig);
this.defaultVestingConfig = this.mergeWithDefaults(configs.defaultVestingConfig || {}, this.defaultVestingConfig);
this.defaultSaleConfig = this.mergeWithDefaults(configs.defaultSaleConfig || {}, this.defaultSaleConfig);
this.defaultGovernanceConfig = this.mergeWithDefaults(configs.defaultGovernanceConfig || {}, this.defaultGovernanceConfig);
}
}
//# sourceMappingURL=ReadWriteFactory.js.map