UNPKG

@0xsplits/splits-sdk

Version:

SDK for the 0xSplits protocol

1,622 lines (1,407 loc) 46.1 kB
import { Address, GetContractReturnType, Hash, InvalidAddressError, Log, decodeEventLog, encodeEventTopics, getAddress, getContract, isAddress, zeroAddress, } from 'viem' import { ARBITRUM_CHAIN_IDS, BASE_CHAIN_IDS, BSC_CHAIN_IDS, ETHEREUM_CHAIN_IDS, GNOSIS_CHAIN_IDS, OPTIMISM_CHAIN_IDS, POLYGON_CHAIN_IDS, SPLITS_SUPPORTED_CHAIN_IDS, TransactionType, ZORA_CHAIN_IDS, getSplitMainAddress, BLAST_CHAIN_IDS, getSplitV1StartBlock, ChainId, splitV1UpdatedEvent, splitV1CreatedEvent, SplitV1CreatedLogType, SplitV1UpdatedLogType, } from '../constants' import { splitMainEthereumAbi, splitMainPolygonAbi, } from '../constants/abi/splitMain' import { AccountNotFoundError, InvalidAuthError, TransactionFailedError, UnsupportedChainIdError, } from '../errors' import type { AcceptControlTransferConfig, BatchDistributeAndWithdrawConfig, BatchDistributeAndWithdrawForAllConfig, CallData, CancelControlTransferConfig, CreateSplitConfig, DistributeTokenConfig, FormattedSplitEarnings, GetSplitBalanceConfig, InitiateControlTransferConfig, MakeSplitImmutableConfig, Split, SplitRecipient, SplitsClientConfig, SplitsPublicClient, TransactionConfig, TransactionFormat, UpdateSplitAndDistributeTokenConfig, UpdateSplitConfig, WithdrawFundsConfig, } from '../types' import { fetchSplitActiveBalances, fromBigIntToPercent, getBigIntFromPercent, getRecipientSortedAddressesAndAllocations, getSplitCreateAndUpdateLogs, } from '../utils' import { validateAddress, validateSplitInputs } from '../utils/validation' import { BaseClientMixin, BaseGasEstimatesMixin, BaseTransactions, } from './base' import { applyMixins } from './mixin' const polygonAbiChainIds = [ ChainId.SEPOLIA, ChainId.HOLESKY, ...POLYGON_CHAIN_IDS, ...OPTIMISM_CHAIN_IDS, ...ARBITRUM_CHAIN_IDS, ...GNOSIS_CHAIN_IDS, ...BSC_CHAIN_IDS, ...ZORA_CHAIN_IDS, ...BASE_CHAIN_IDS, ...BLAST_CHAIN_IDS, ] type SplitMainEthereumAbiType = typeof splitMainEthereumAbi class SplitV1Transactions extends BaseTransactions { constructor(transactionClientArgs: SplitsClientConfig & TransactionConfig) { super({ supportedChainIds: SPLITS_SUPPORTED_CHAIN_IDS, ...transactionClientArgs, }) } protected async _createSplitTransaction({ recipients, distributorFeePercent, controller = zeroAddress, chainId, transactionOverrides = {}, }: CreateSplitConfig): Promise<TransactionFormat> { validateSplitInputs({ recipients, distributorFeePercent, controller }) if (this._shouldRequireWalletClient) this._requireWalletClient() const [accounts, percentAllocations] = getRecipientSortedAddressesAndAllocations(recipients) const distributorFee = getBigIntFromPercent(distributorFeePercent) const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'createSplit', functionArgs: [accounts, percentAllocations, distributorFee, controller], transactionOverrides, }) return result } protected async _updateSplitTransaction({ splitAddress, recipients, distributorFeePercent, chainId, transactionOverrides = {}, }: UpdateSplitConfig): Promise<TransactionFormat> { validateAddress(splitAddress) validateSplitInputs({ recipients, distributorFeePercent }) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const [accounts, percentAllocations] = getRecipientSortedAddressesAndAllocations(recipients) const distributorFee = getBigIntFromPercent(distributorFeePercent) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'updateSplit', functionArgs: [ splitAddress, accounts, percentAllocations, distributorFee, ], transactionOverrides, }) return result } protected async _distributeTokenTransaction({ splitAddress, token, distributorAddress, chainId, splitFields, transactionOverrides = {}, }: DistributeTokenConfig): Promise<TransactionFormat> { validateAddress(splitAddress) validateAddress(token) if (this._shouldRequireWalletClient) this._requireWalletClient() const distributorPayoutAddress = distributorAddress ? distributorAddress : this._walletClient?.account ? this._walletClient.account.address : zeroAddress validateAddress(distributorPayoutAddress) const functionChainId = this._getFunctionChainId(chainId) let split: Pick<Split, 'recipients' | 'distributorFeePercent'> if (splitFields) { split = splitFields } else { this._requireDataClient() split = await this._dataClient!.getSplitMetadata({ chainId: functionChainId, splitAddress, }) } const [accounts, percentAllocations] = getRecipientSortedAddressesAndAllocations( split.recipients.map((recipient) => { return { percentAllocation: recipient.percentAllocation, address: recipient.recipient.address, } }), ) const distributorFee = getBigIntFromPercent(split.distributorFeePercent) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: token === zeroAddress ? 'distributeETH' : 'distributeERC20', functionArgs: token === zeroAddress ? [ splitAddress, accounts, percentAllocations, distributorFee, distributorPayoutAddress, ] : [ splitAddress, token, accounts, percentAllocations, distributorFee, distributorPayoutAddress, ], transactionOverrides, }) return result } protected async _updateSplitAndDistributeTokenTransaction({ splitAddress, token, recipients, distributorFeePercent, distributorAddress, chainId, transactionOverrides = {}, }: UpdateSplitAndDistributeTokenConfig): Promise<TransactionFormat> { validateAddress(splitAddress) validateAddress(token) validateSplitInputs({ recipients, distributorFeePercent }) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const [accounts, percentAllocations] = getRecipientSortedAddressesAndAllocations(recipients) const distributorFee = getBigIntFromPercent(distributorFeePercent) const distributorPayoutAddress = distributorAddress ? distributorAddress : this._walletClient?.account ? this._walletClient.account.address : zeroAddress validateAddress(distributorPayoutAddress) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: token === zeroAddress ? 'updateAndDistributeETH' : 'updateAndDistributeERC20', functionArgs: token === zeroAddress ? [ splitAddress, accounts, percentAllocations, distributorFee, distributorPayoutAddress, ] : [ splitAddress, token, accounts, percentAllocations, distributorFee, distributorPayoutAddress, ], transactionOverrides, }) return result } protected async _withdrawFundsTransaction({ address, tokens, chainId, transactionOverrides = {}, }: WithdrawFundsConfig): Promise<TransactionFormat> { validateAddress(address) if (this._shouldRequireWalletClient) this._requireWalletClient() const withdrawEth = tokens.includes(zeroAddress) ? 1 : 0 const erc20s = tokens.filter((token) => token !== zeroAddress) const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'withdraw', functionArgs: [address, withdrawEth, erc20s], transactionOverrides, }) return result } protected async _initiateControlTransferTransaction({ splitAddress, newController, chainId, transactionOverrides = {}, }: InitiateControlTransferConfig): Promise<TransactionFormat> { validateAddress(splitAddress) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'transferControl', functionArgs: [splitAddress, newController], transactionOverrides, }) return result } protected async _cancelControlTransferTransaction({ splitAddress, chainId, transactionOverrides = {}, }: CancelControlTransferConfig): Promise<TransactionFormat> { validateAddress(splitAddress) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'cancelControlTransfer', functionArgs: [splitAddress], transactionOverrides, }) return result } protected async _acceptControlTransferTransaction({ splitAddress, chainId, transactionOverrides = {}, }: AcceptControlTransferConfig): Promise<TransactionFormat> { validateAddress(splitAddress) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireNewPotentialController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'acceptControl', functionArgs: [splitAddress], transactionOverrides, }) return result } protected async _makeSplitImmutableTransaction({ splitAddress, chainId, transactionOverrides = {}, }: MakeSplitImmutableConfig): Promise<TransactionFormat> { validateAddress(splitAddress) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireController(splitAddress) } const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getSplitMainAddress(functionChainId), contractAbi: this._getSplitMainAbi(functionChainId), functionName: 'makeSplitImmutable', functionArgs: [splitAddress], transactionOverrides, }) return result } protected async _batchDistributeAndWithdrawTransaction( { splitAddress, tokens, recipientAddresses, distributorAddress, }: BatchDistributeAndWithdrawConfig, distributeFunc: (args: DistributeTokenConfig) => Promise<CallData>, withdrawFunc: (args: WithdrawFundsConfig) => Promise<CallData>, ): Promise<TransactionFormat> { validateAddress(splitAddress) tokens.map((token) => validateAddress(token)) recipientAddresses.map((address) => validateAddress(address)) if (this._shouldRequireWalletClient) { this._requireWalletClient() } const distributorPayoutAddress = distributorAddress ? distributorAddress : this._walletClient?.account ? this._walletClient.account.address : zeroAddress validateAddress(distributorPayoutAddress) const distributeCalls = await Promise.all( tokens.map(async (token) => { return await distributeFunc({ splitAddress, token, distributorAddress: distributorPayoutAddress, }) }), ) const withdrawCalls = await Promise.all( recipientAddresses.map(async (address) => { return await withdrawFunc({ address, tokens }) }), ) const multicallData = [...distributeCalls, ...withdrawCalls] const result = await this._multicallTransaction({ calls: multicallData }) return result } protected async _batchDistributeAndWithdrawForAllTransaction( { splitAddress, tokens, distributorAddress, chainId, }: BatchDistributeAndWithdrawForAllConfig, distributeFunc: (args: DistributeTokenConfig) => Promise<CallData>, withdrawFunc: (args: WithdrawFundsConfig) => Promise<CallData>, ): Promise<TransactionFormat> { validateAddress(splitAddress) tokens.map((token) => validateAddress(token)) if (this._shouldRequireWalletClient) { this._requireWalletClient() } this._requireDataClient() const functionChainId = this._getFunctionChainId(chainId) const { recipients } = await this._dataClient!.getSplitMetadata({ chainId: functionChainId, splitAddress, }) const recipientAddresses = recipients.map( (recipient) => recipient.recipient.address, ) const result = await this._batchDistributeAndWithdrawTransaction( { splitAddress, tokens, recipientAddresses, distributorAddress, }, distributeFunc, withdrawFunc, ) return result } protected async _checkForSplitExistence({ splitAddress, chainId, }: { splitAddress: Address chainId: number }): Promise<void> { const formattedSplitAddress = getAddress(splitAddress) const splitMainContract = this._getSplitMainContract(chainId) const splitHash = await splitMainContract.read.getHash([ formattedSplitAddress, ]) if ( splitHash === '0x0000000000000000000000000000000000000000000000000000000000000000' ) { // Split does not exist throw new AccountNotFoundError('Split', formattedSplitAddress, chainId) } } protected async _getSplitMetadataViaProvider({ splitAddress, chainId, cachedData, }: { splitAddress: Address chainId: number cachedData?: { blocks?: { createBlock: bigint updateBlock?: bigint latestScannedBlock: bigint } blockRange?: bigint } }): Promise<{ split: Split; blockRange: bigint }> { if (chainId === ChainId.MAINNET) throw new Error('Mainnet not supported for provider metadata') const formattedSplitAddress = getAddress(splitAddress) const publicClient = this._getPublicClient(chainId) await this._checkForSplitExistence({ splitAddress, chainId }) const { blockRange, createLog, updateLog } = await getSplitCreateAndUpdateLogs<'CreateSplit', 'UpdateSplit'>({ splitAddress, publicClient, splitCreatedEvent: splitV1CreatedEvent, splitUpdatedEvent: splitV1UpdatedEvent, addresses: [getSplitMainAddress(chainId)], startBlockNumber: getSplitV1StartBlock(chainId), cachedBlocks: cachedData?.blocks, defaultBlockRange: cachedData?.blockRange, }) if (!createLog) throw new AccountNotFoundError( 'Split', formattedSplitAddress, publicClient.chain!.id, ) const split = await this._buildSplitFromLogs({ splitAddress: formattedSplitAddress, chainId, createLog, updateLog: updateLog ? (updateLog as SplitV1UpdatedLogType) : undefined, }) return { split, blockRange } } protected async _buildSplitFromLogs({ splitAddress, chainId, createLog, updateLog, }: { splitAddress: Address chainId: number createLog: SplitV1CreatedLogType updateLog?: SplitV1UpdatedLogType }): Promise<Split> { const splitMainContract = this._getSplitMainContract(chainId) const controller = await splitMainContract.read.getController([ getAddress(splitAddress), ]) const recipients = createLog.args.accounts.map((recipient, i) => { return { recipient: { address: recipient, }, ownership: BigInt(createLog.args.percentAllocations[i]), percentAllocation: fromBigIntToPercent( BigInt(createLog.args.percentAllocations[i]), BigInt(1_000_000), ), } }) const split: Split = { address: splitAddress, totalOwnership: BigInt(1_000_000), recipients, distributorFeePercent: fromBigIntToPercent( BigInt(createLog.args.distributorFee), ), distributeDirection: 'pull', type: 'Split', controller: { address: controller, }, distributionsPaused: false, newPotentialController: { address: zeroAddress, }, createdBlock: Number(createLog.blockNumber), updateBlock: Number(createLog.blockNumber), } if (!updateLog) return split const updatedRecipients = updateLog.args.accounts.map((recipient, i) => { return { recipient: { address: recipient, }, ownership: BigInt(updateLog.args.percentAllocations[i]), percentAllocation: fromBigIntToPercent( BigInt(updateLog.args.percentAllocations[i]), BigInt(1_000_000), ), } }) split.recipients = updatedRecipients split.distributorFeePercent = fromBigIntToPercent( BigInt(updateLog.args.distributorFee), ) split.updateBlock = Number(updateLog.blockNumber) return split } private async _requireController(splitAddress: string) { const chainId = this._walletClient!.chain!.id const splitMainContract = this._getSplitMainContract(chainId) const controller = await splitMainContract.read.getController([ getAddress(splitAddress), ]) const walletAddress = this._walletClient!.account?.address if (controller.toLowerCase() !== walletAddress?.toLowerCase()) throw new InvalidAuthError( `Action only available to the split controller. Split id: ${splitAddress}, split controller: ${controller}, wallet address: ${walletAddress}`, ) } private async _requireNewPotentialController(splitAddress: string) { const chainId = this._walletClient!.chain!.id const splitMainContract = this._getSplitMainContract(chainId) const newPotentialController = await splitMainContract.read.getNewPotentialController([ getAddress(splitAddress), ]) const walletAddress = this._walletClient!.account?.address if (newPotentialController.toLowerCase() !== walletAddress?.toLowerCase()) throw new InvalidAuthError( `Action only available to the split's new potential controller. Split new potential controller: ${newPotentialController}. Wallet address: ${walletAddress}`, ) } protected _getSplitMainContract( chainId: number, ): GetContractReturnType<SplitMainEthereumAbiType, SplitsPublicClient> { const publicClient = this._getPublicClient(chainId) return getContract({ address: getSplitMainAddress(chainId), abi: splitMainEthereumAbi, client: publicClient, }) } protected _getSplitMainAbi(chainId: number) { if (ETHEREUM_CHAIN_IDS.includes(chainId)) { return splitMainEthereumAbi } else if (polygonAbiChainIds.includes(chainId)) { return splitMainPolygonAbi } else throw new UnsupportedChainIdError(chainId, SPLITS_SUPPORTED_CHAIN_IDS) } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class SplitV1Client extends SplitV1Transactions { readonly callData: SplitV1CallData readonly estimateGas: SplitV1GasEstimates constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.Transaction, ...clientArgs, }) this.callData = new SplitV1CallData(clientArgs) this.estimateGas = new SplitV1GasEstimates(clientArgs) } getEventTopics(chainId: number) { const splitMainAbi = this._getSplitMainAbi(chainId) return { createSplit: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'CreateSplit', })[0], ], updateSplit: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'UpdateSplit', })[0], ], distributeToken: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'DistributeETH', })[0], encodeEventTopics({ abi: splitMainAbi, eventName: 'DistributeERC20', })[0], ], updateSplitAndDistributeToken: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'UpdateSplit', })[0], encodeEventTopics({ abi: splitMainAbi, eventName: 'DistributeETH', })[0], encodeEventTopics({ abi: splitMainAbi, eventName: 'DistributeERC20', })[0], ], withdrawFunds: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'Withdrawal', })[0], ], initiateControlTransfer: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'InitiateControlTransfer', })[0], ], cancelControlTransfer: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'CancelControlTransfer', })[0], ], acceptControlTransfer: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'ControlTransfer', })[0], ], makeSplitImmutable: [ encodeEventTopics({ abi: splitMainAbi, eventName: 'ControlTransfer', })[0], ], } } /* / / SPLIT ACTIONS / */ // Write actions async _submitCreateSplitTransaction( createSplitArgs: CreateSplitConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._createSplitTransaction(createSplitArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async createSplit(createSplitArgs: CreateSplitConfig): Promise<{ splitAddress: Address event: Log }> { const { txHash } = await this._submitCreateSplitTransaction(createSplitArgs) const functionChainId = this._getFunctionChainId(createSplitArgs.chainId) const eventTopics = this.getEventTopics( this._getFunctionChainId(functionChainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.createSplit, }) const event = events.length > 0 ? events[0] : undefined if (event) { if (ETHEREUM_CHAIN_IDS.includes(functionChainId)) { const log = decodeEventLog({ abi: splitMainEthereumAbi, data: event.data, topics: event.topics, }) if (log.eventName !== 'CreateSplit') throw new Error() return { splitAddress: log.args.split, event, } } else { const log = decodeEventLog({ abi: splitMainPolygonAbi, data: event.data, topics: event.topics, }) if (log.eventName !== 'CreateSplit') throw new Error() return { splitAddress: log.args.split, event, } } } throw new TransactionFailedError() } async _submitUpdateSplitTransaction( updateSplitArgs: UpdateSplitConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._updateSplitTransaction(updateSplitArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async updateSplit(updateSplitArgs: UpdateSplitConfig): Promise<{ event: Log }> { const { txHash } = await this._submitUpdateSplitTransaction(updateSplitArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(updateSplitArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.updateSplit, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitDistributeTokenTransaction( distributeTokenArgs: DistributeTokenConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._distributeTokenTransaction(distributeTokenArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async distributeToken(distributeTokenArgs: DistributeTokenConfig): Promise<{ event: Log }> { const { txHash } = await this._submitDistributeTokenTransaction(distributeTokenArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(distributeTokenArgs.chainId), ) const { token } = distributeTokenArgs const eventTopic = token === zeroAddress ? eventTopics.distributeToken[0] : eventTopics.distributeToken[1] const events = await this.getTransactionEvents({ txHash, eventTopics: [eventTopic], }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitUpdateSplitAndDistributeTokenTransaction( updateAndDistributeArgs: UpdateSplitAndDistributeTokenConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._updateSplitAndDistributeTokenTransaction( updateAndDistributeArgs, ) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async updateSplitAndDistributeToken( updateAndDistributeArgs: UpdateSplitAndDistributeTokenConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitUpdateSplitAndDistributeTokenTransaction( updateAndDistributeArgs, ) const eventTopics = this.getEventTopics( this._getFunctionChainId(updateAndDistributeArgs.chainId), ) const { token } = updateAndDistributeArgs const eventTopic = token === zeroAddress ? eventTopics.updateSplitAndDistributeToken[1] : eventTopics.updateSplitAndDistributeToken[2] const events = await this.getTransactionEvents({ txHash, eventTopics: [eventTopic], }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitWithdrawFundsTransaction( withdrawArgs: WithdrawFundsConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._withdrawFundsTransaction(withdrawArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async withdrawFunds(withdrawArgs: WithdrawFundsConfig): Promise<{ event: Log }> { const { txHash } = await this._submitWithdrawFundsTransaction(withdrawArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(withdrawArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.withdrawFunds, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitInitiateControlTransferTransaction( initiateTransferArgs: InitiateControlTransferConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._initiateControlTransferTransaction(initiateTransferArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async initiateControlTransfer( initiateTransferArgs: InitiateControlTransferConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitInitiateControlTransferTransaction(initiateTransferArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(initiateTransferArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.initiateControlTransfer, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitCancelControlTransferTransaction( cancelTransferArgs: CancelControlTransferConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._cancelControlTransferTransaction(cancelTransferArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async cancelControlTransfer( cancelTransferArgs: CancelControlTransferConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitCancelControlTransferTransaction(cancelTransferArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(cancelTransferArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.cancelControlTransfer, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitAcceptControlTransferTransaction( acceptTransferArgs: AcceptControlTransferConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._acceptControlTransferTransaction(acceptTransferArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async acceptControlTransfer( acceptTransferArgs: AcceptControlTransferConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitAcceptControlTransferTransaction(acceptTransferArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(acceptTransferArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.acceptControlTransfer, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async _submitMakeSplitImmutableTransaction( makeImmutableArgs: MakeSplitImmutableConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._makeSplitImmutableTransaction(makeImmutableArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async makeSplitImmutable( makeImmutableArgs: MakeSplitImmutableConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitMakeSplitImmutableTransaction(makeImmutableArgs) const eventTopics = this.getEventTopics( this._getFunctionChainId(makeImmutableArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.makeSplitImmutable, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event } throw new TransactionFailedError() } async batchDistributeAndWithdraw( batchDistributeAndWithdrawArgs: BatchDistributeAndWithdrawConfig, ): Promise<{ events: Log[] }> { const txHash = await this._batchDistributeAndWithdrawTransaction( batchDistributeAndWithdrawArgs, this.callData.distributeToken.bind(this.callData), this.callData.withdrawFunds.bind(this.callData), ) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') const eventTopics = this.getEventTopics( this._getFunctionChainId(batchDistributeAndWithdrawArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.distributeToken.concat( eventTopics.withdrawFunds, ), }) return { events } } async batchDistributeAndWithdrawForAll( batchDistributeAndWithdrawForAllArgs: BatchDistributeAndWithdrawForAllConfig, ): Promise<{ events: Log[] }> { const txHash = await this._batchDistributeAndWithdrawForAllTransaction( batchDistributeAndWithdrawForAllArgs, this.callData.distributeToken.bind(this.callData), this.callData.withdrawFunds.bind(this.callData), ) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') const eventTopics = this.getEventTopics( this._getFunctionChainId(batchDistributeAndWithdrawForAllArgs.chainId), ) const events = await this.getTransactionEvents({ txHash, eventTopics: eventTopics.distributeToken.concat( eventTopics.withdrawFunds, ), }) return { events } } // Read actions async getSplitBalance({ splitAddress, token = zeroAddress, chainId, }: GetSplitBalanceConfig): Promise<{ balance: bigint }> { validateAddress(splitAddress) validateAddress(token) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitMainContract = this._getSplitMainContract(functionChainId) const balance = token === zeroAddress ? await splitMainContract.read.getETHBalance([getAddress(splitAddress)]) : await splitMainContract.read.getERC20Balance([ getAddress(splitAddress), getAddress(token), ]) return { balance } } async predictImmutableSplitAddress({ recipients, distributorFeePercent, chainId, }: { recipients: SplitRecipient[] distributorFeePercent: number chainId?: number }): Promise<{ splitAddress: Address splitExists: boolean }> { validateSplitInputs({ recipients, distributorFeePercent }) const [accounts, percentAllocations] = getRecipientSortedAddressesAndAllocations(recipients) const distributorFee = getBigIntFromPercent(distributorFeePercent) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitMainContract = this._getSplitMainContract(functionChainId) const splitAddress = await splitMainContract.read.predictImmutableSplitAddress([ accounts, percentAllocations.map((p) => Number(p)), Number(distributorFee), ]) const { hash } = await this.getHash({ splitAddress }) const splitExists = hash !== '0x0000000000000000000000000000000000000000000000000000000000000000' return { splitAddress, splitExists } } async getController({ splitAddress, chainId, }: { splitAddress: string chainId?: number }): Promise<{ controller: Address }> { validateAddress(splitAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitMainContract = this._getSplitMainContract(functionChainId) const controller = await splitMainContract.read.getController([ getAddress(splitAddress), ]) return { controller } } async getNewPotentialController({ splitAddress, chainId, }: { splitAddress: string chainId?: number }): Promise<{ newPotentialController: Address }> { validateAddress(splitAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitMainContract = this._getSplitMainContract(functionChainId) const newPotentialController = await splitMainContract.read.getNewPotentialController([ getAddress(splitAddress), ]) return { newPotentialController } } async getHash({ splitAddress, chainId, }: { splitAddress: string chainId?: number }): Promise<{ hash: string }> { validateAddress(splitAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitMainContract = this._getSplitMainContract(functionChainId) const hash = await splitMainContract.read.getHash([ getAddress(splitAddress), ]) return { hash } } async _doesSplitExist({ splitAddress, chainId, }: { splitAddress: Address chainId: number }) { try { await this._checkForSplitExistence({ splitAddress, chainId }) return true } catch { return false } } async _getSplitFromLogs({ splitAddress, chainId, createLog, updateLog, }: { splitAddress: Address chainId: number createLog: SplitV1CreatedLogType updateLog?: SplitV1UpdatedLogType }) { return await this._buildSplitFromLogs({ splitAddress, chainId, createLog, updateLog, }) } async getSplitMetadataViaProvider({ splitAddress, chainId, cachedData, }: { splitAddress: string chainId?: number cachedData?: { blocks?: { createBlock: bigint updateBlock?: bigint latestScannedBlock: bigint } blockRange?: bigint } }): Promise<{ split: Split; blockRange: bigint }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) if (!isAddress(splitAddress)) throw new InvalidAddressError({ address: splitAddress }) const { split, blockRange } = await this._getSplitMetadataViaProvider({ splitAddress, chainId: functionChainId, cachedData, }) return { split, blockRange } } async getSplitActiveBalances({ splitAddress, chainId, erc20TokenList, }: { splitAddress: string chainId?: number erc20TokenList?: string[] }): Promise<Pick<FormattedSplitEarnings, 'activeBalances'>> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) const fullTokenList = [ zeroAddress, ...(erc20TokenList ? erc20TokenList : []), ] const publicClient = this._getPublicClient(functionChainId) if (!isAddress(splitAddress)) throw new InvalidAddressError({ address: splitAddress }) await this._checkForSplitExistence({ splitAddress, chainId: functionChainId, }) const activeBalances = await fetchSplitActiveBalances({ type: 'splitV1', chainId: functionChainId, splitAddress, publicClient, fullTokenList: fullTokenList.map(getAddress), }) return { activeBalances, } } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface SplitV1Client extends BaseClientMixin {} applyMixins(SplitV1Client, [BaseClientMixin]) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class SplitV1GasEstimates extends SplitV1Transactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.GasEstimate, ...clientArgs, }) } async createSplit(createSplitArgs: CreateSplitConfig): Promise<bigint> { const gasEstimate = await this._createSplitTransaction(createSplitArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async updateSplit(updateSplitArgs: UpdateSplitConfig): Promise<bigint> { const gasEstimate = await this._updateSplitTransaction(updateSplitArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async distributeToken( distributeTokenArgs: DistributeTokenConfig, ): Promise<bigint> { const gasEstimate = await this._distributeTokenTransaction(distributeTokenArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async updateSplitAndDistributeToken( updateAndDistributeArgs: UpdateSplitAndDistributeTokenConfig, ): Promise<bigint> { const gasEstimate = await this._updateSplitAndDistributeTokenTransaction( updateAndDistributeArgs, ) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async withdrawFunds(withdrawArgs: WithdrawFundsConfig): Promise<bigint> { const gasEstimate = await this._withdrawFundsTransaction(withdrawArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async initiateControlTransfer( initiateTransferArgs: InitiateControlTransferConfig, ): Promise<bigint> { const gasEstimate = await this._initiateControlTransferTransaction(initiateTransferArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async cancelControlTransfer( cancelTransferArgs: CancelControlTransferConfig, ): Promise<bigint> { const gasEstimate = await this._cancelControlTransferTransaction(cancelTransferArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async acceptControlTransfer( acceptTransferArgs: AcceptControlTransferConfig, ): Promise<bigint> { const gasEstimate = await this._acceptControlTransferTransaction(acceptTransferArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async makeSplitImmutable( makeImmutableArgs: MakeSplitImmutableConfig, ): Promise<bigint> { const gasEstimate = await this._makeSplitImmutableTransaction(makeImmutableArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging interface SplitV1GasEstimates extends BaseGasEstimatesMixin {} applyMixins(SplitV1GasEstimates, [BaseGasEstimatesMixin]) class SplitV1CallData extends SplitV1Transactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.CallData, ...clientArgs, }) } async createSplit(createSplitArgs: CreateSplitConfig): Promise<CallData> { const callData = await this._createSplitTransaction(createSplitArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async updateSplit(updateSplitArgs: UpdateSplitConfig): Promise<CallData> { const callData = await this._updateSplitTransaction(updateSplitArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async distributeToken( distributeTokenArgs: DistributeTokenConfig, ): Promise<CallData> { const callData = await this._distributeTokenTransaction(distributeTokenArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async updateSplitAndDistributeToken( updateAndDistributeArgs: UpdateSplitAndDistributeTokenConfig, ): Promise<CallData> { const callData = await this._updateSplitAndDistributeTokenTransaction( updateAndDistributeArgs, ) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async withdrawFunds(withdrawArgs: WithdrawFundsConfig): Promise<CallData> { const callData = await this._withdrawFundsTransaction(withdrawArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async initiateControlTransfer( initiateTransferArgs: InitiateControlTransferConfig, ): Promise<CallData> { const callData = await this._initiateControlTransferTransaction(initiateTransferArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async cancelControlTransfer( cancelTransferArgs: CancelControlTransferConfig, ): Promise<CallData> { const callData = await this._cancelControlTransferTransaction(cancelTransferArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async acceptControlTransfer( acceptTransferArgs: AcceptControlTransferConfig, ): Promise<CallData> { const callData = await this._acceptControlTransferTransaction(acceptTransferArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async makeSplitImmutable( makeImmutableArgs: MakeSplitImmutableConfig, ): Promise<CallData> { const callData = await this._makeSplitImmutableTransaction(makeImmutableArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async batchDistributeAndWithdraw( batchDistributeAndWithdrawArgs: BatchDistributeAndWithdrawConfig, ): Promise<CallData> { const callData = await this._batchDistributeAndWithdrawTransaction( batchDistributeAndWithdrawArgs, this.distributeToken.bind(this), this.withdrawFunds.bind(this), ) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async batchDistributeAndWithdrawForAll( batchDistributeAndWithdrawForAllArgs: BatchDistributeAndWithdrawForAllConfig, ): Promise<CallData> { const callData = await this._batchDistributeAndWithdrawForAllTransaction( batchDistributeAndWithdrawForAllArgs, this.distributeToken.bind(this), this.withdrawFunds.bind(this), ) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } }