UNPKG

@0xsplits/splits-sdk

Version:

SDK for the 0xSplits protocol

455 lines (392 loc) 12.4 kB
import { Address, GetContractReturnType, Hash, Hex, Log, decodeEventLog, encodeEventTopics, getAddress, getContract, } from 'viem' import { BaseClientMixin, BaseGasEstimatesMixin, BaseTransactions, } from './base' import { TransactionType, VESTING_CHAIN_IDS, getVestingFactoryAddress, } from '../constants' import { vestingFactoryAbi } from '../constants/abi/vestingFactory' import { vestingAbi } from '../constants/abi/vesting' import { TransactionFailedError } from '../errors' import { applyMixins } from './mixin' import type { CallData, CreateVestingConfig, ReadContractArgs, ReleaseVestedFundsConfig, SplitsClientConfig, StartVestConfig, TransactionConfig, TransactionFormat, SplitsPublicClient, } from '../types' import { validateAddress, validateVestingPeriod } from '../utils/validation' type VestingAbi = typeof vestingAbi type VestingFactoryAbi = typeof vestingFactoryAbi class VestingTransactions extends BaseTransactions { constructor(transactionClientArgs: SplitsClientConfig & TransactionConfig) { super({ supportedChainIds: VESTING_CHAIN_IDS, ...transactionClientArgs, }) } protected async _createVestingModuleTransaction({ beneficiary, vestingPeriodSeconds, chainId, transactionOverrides = {}, }: CreateVestingConfig): Promise<TransactionFormat> { validateAddress(beneficiary) validateVestingPeriod(vestingPeriodSeconds) if (this._shouldRequireWalletClient) this._requireWalletClient() const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getVestingFactoryAddress(functionChainId), contractAbi: vestingFactoryAbi, functionName: 'createVestingModule', functionArgs: [beneficiary, vestingPeriodSeconds], transactionOverrides, }) return result } protected async _startVestTransaction({ vestingModuleAddress, tokens, transactionOverrides = {}, }: StartVestConfig): Promise<TransactionFormat> { validateAddress(vestingModuleAddress) tokens.map((token) => validateAddress(token)) if (this._shouldRequireWalletClient) this._requireWalletClient() const result = await this._executeContractFunction({ contractAddress: getAddress(vestingModuleAddress), contractAbi: vestingAbi, functionName: 'createVestingStreams', functionArgs: [tokens], transactionOverrides, }) return result } protected async _releaseVestedFundsTransaction({ vestingModuleAddress, streamIds, transactionOverrides = {}, }: ReleaseVestedFundsConfig): Promise<TransactionFormat> { validateAddress(vestingModuleAddress) if (this._shouldRequireWalletClient) this._requireWalletClient() const result = await this._executeContractFunction({ contractAddress: getAddress(vestingModuleAddress), contractAbi: vestingAbi, functionName: 'releaseFromVesting', functionArgs: [streamIds], transactionOverrides, }) return result } protected _getVestingContract( vestingModule: string, chainId: number, ): GetContractReturnType<VestingAbi, SplitsPublicClient> { const publicClient = this._getPublicClient(chainId) return getContract({ address: getAddress(vestingModule), abi: vestingAbi, client: publicClient, }) } protected _getVestingFactoryContract( chainId: number, ): GetContractReturnType<VestingFactoryAbi, SplitsPublicClient> { const publicClient = this._getPublicClient(chainId) return getContract({ address: getVestingFactoryAddress(chainId), abi: vestingFactoryAbi, client: publicClient, }) } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class VestingClient extends VestingTransactions { readonly eventTopics: { [key: string]: Hex[] } readonly callData: VestingCallData readonly estimateGas: VestingGasEstimates constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.Transaction, ...clientArgs, }) this.eventTopics = { createVestingModule: [ encodeEventTopics({ abi: vestingFactoryAbi, eventName: 'CreateVestingModule', })[0], ], startVest: [ encodeEventTopics({ abi: vestingAbi, eventName: 'CreateVestingStream', })[0], ], releaseVestedFunds: [ encodeEventTopics({ abi: vestingAbi, eventName: 'ReleaseFromVestingStream', })[0], ], } this.callData = new VestingCallData(clientArgs) this.estimateGas = new VestingGasEstimates(clientArgs) } // Write actions async _submitCreateVestingModuleTransaction( createVestingArgs: CreateVestingConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._createVestingModuleTransaction(createVestingArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async createVestingModule(createVestingArgs: CreateVestingConfig): Promise<{ vestingModuleAddress: string event: Log }> { const { txHash } = await this._submitCreateVestingModuleTransaction(createVestingArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.createVestingModule, }) const event = events.length > 0 ? events[0] : undefined if (event) { const log = decodeEventLog({ abi: vestingFactoryAbi, data: event.data, topics: event.topics, }) return { vestingModuleAddress: log.args.vestingModule, event, } } throw new TransactionFailedError() } async _submitStartVestTransaction(startVestArgs: StartVestConfig): Promise<{ txHash: Hash }> { const txHash = await this._startVestTransaction(startVestArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async startVest(startVestArgs: StartVestConfig): Promise<{ events: Log[] }> { const { txHash } = await this._submitStartVestTransaction(startVestArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.startVest, }) return { events } } async _submitReleaseVestedFundsTransaction( releaseFundsArgs: ReleaseVestedFundsConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._releaseVestedFundsTransaction(releaseFundsArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async releaseVestedFunds( releaseFundsArgs: ReleaseVestedFundsConfig, ): Promise<{ events: Log[] }> { const { txHash } = await this._submitReleaseVestedFundsTransaction(releaseFundsArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.releaseVestedFunds, }) return { events } } // Read actions async predictVestingModuleAddress({ beneficiary, vestingPeriodSeconds, chainId, }: CreateVestingConfig): Promise<{ address: Address exists: boolean }> { validateAddress(beneficiary) validateVestingPeriod(vestingPeriodSeconds) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const vestingModuleFactoryContract = this._getVestingFactoryContract(functionChainId) const [address, exists] = await vestingModuleFactoryContract.read.predictVestingModuleAddress([ getAddress(beneficiary), BigInt(vestingPeriodSeconds), ]) return { address, exists, } } async getBeneficiary({ vestingModuleAddress, chainId, }: ReadContractArgs & { vestingModuleAddress: string }): Promise<{ beneficiary: Address }> { validateAddress(vestingModuleAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const vestingContract = this._getVestingContract( vestingModuleAddress, functionChainId, ) const beneficiary = await vestingContract.read.beneficiary() return { beneficiary } } async getVestingPeriod({ vestingModuleAddress, chainId, }: ReadContractArgs & { vestingModuleAddress: string }): Promise<{ vestingPeriod: bigint }> { validateAddress(vestingModuleAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const vestingContract = this._getVestingContract( vestingModuleAddress, functionChainId, ) const vestingPeriod = await vestingContract.read.vestingPeriod() return { vestingPeriod } } async getVestedAmount({ vestingModuleAddress, streamId, chainId, }: ReadContractArgs & { vestingModuleAddress: string streamId: string }): Promise<{ amount: bigint }> { validateAddress(vestingModuleAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const vestingContract = this._getVestingContract( vestingModuleAddress, functionChainId, ) const amount = await vestingContract.read.vested([BigInt(streamId)]) return { amount } } async getVestedAndUnreleasedAmount({ vestingModuleAddress, streamId, chainId, }: ReadContractArgs & { vestingModuleAddress: string streamId: string }): Promise<{ amount: bigint }> { validateAddress(vestingModuleAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const vestingContract = this._getVestingContract( vestingModuleAddress, functionChainId, ) const amount = await vestingContract.read.vestedAndUnreleased([ BigInt(streamId), ]) return { amount } } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface VestingClient extends BaseClientMixin {} applyMixins(VestingClient, [BaseClientMixin]) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class VestingGasEstimates extends VestingTransactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.GasEstimate, ...clientArgs, }) } async createVestingModule( createVestingArgs: CreateVestingConfig, ): Promise<bigint> { const gasEstimate = await this._createVestingModuleTransaction(createVestingArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async startVest(startVestArgs: StartVestConfig): Promise<bigint> { const gasEstimate = await this._startVestTransaction(startVestArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async releaseVestedFunds( releaseFundsArgs: ReleaseVestedFundsConfig, ): Promise<bigint> { const gasEstimate = await this._releaseVestedFundsTransaction(releaseFundsArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging interface VestingGasEstimates extends BaseGasEstimatesMixin {} applyMixins(VestingGasEstimates, [BaseGasEstimatesMixin]) class VestingCallData extends VestingTransactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.CallData, ...clientArgs, }) } async createVestingModule( createVestingArgs: CreateVestingConfig, ): Promise<CallData> { const callData = await this._createVestingModuleTransaction(createVestingArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async startVest(startVestArgs: StartVestConfig): Promise<CallData> { const callData = await this._startVestTransaction(startVestArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async releaseVestedFunds( releaseFundsArgs: ReleaseVestedFundsConfig, ): Promise<CallData> { const callData = await this._releaseVestedFundsTransaction(releaseFundsArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } }