UNPKG

@0xsplits/splits-sdk

Version:

SDK for the 0xSplits protocol

1,406 lines (1,234 loc) 37.1 kB
import { Address, GetContractReturnType, Hash, Hex, InvalidAddressError, Log, TypedDataDomain, decodeEventLog, encodeEventTopics, getAddress, getContract, isAddress, zeroAddress, } from 'viem' import { NATIVE_TOKEN_ADDRESS, SPLITS_SUBGRAPH_CHAIN_IDS, SPLITS_V2_SUPPORTED_CHAIN_IDS, SplitV2CreatedLogType, SplitV2UpdatedLogType, TransactionType, ZERO, getSplitV2FactoriesStartBlock, getSplitV2FactoryAddress, getSplitV2o1FactoryAddress, getSplitV2o2FactoryAddress, splitV2CreatedEvent, splitV2UpdatedEvent, } from '../constants' import { splitV2ABI } from '../constants/abi/splitV2' import { splitV2FactoryABI } from '../constants/abi/splitV2Factory' import { AccountNotFoundError, InvalidAuthError, SaltRequired, TransactionFailedError, } from '../errors' import { CallData, CreateSplitV2Config, DistributeSplitConfig, FormattedSplitEarnings, SetPausedConfig, Split, SplitV2ExecCallsConfig, SplitV2Type, SplitsClientConfig, SplitsPublicClient, TransactionConfig, TransactionFormat, TransferOwnershipConfig, UpdateSplitV2Config, } from '../types' import { fetchSplitActiveBalances, fromBigIntToPercent, getNumberFromPercent, getSplitType, getValidatedSplitV2Config, validateAddress, getSplitCreateAndUpdateLogs, MAX_PULL_SPLIT_RECIPIENTS, MAX_PUSH_SPLIT_RECIPIENTS, getSplitV2TypeFromBytecode, getMaxSplitV2Recipients, } from '../utils' import { BaseClientMixin, BaseGasEstimatesMixin, BaseTransactions, } from './base' import { applyMixins } from './mixin' import { SplitV2Versions } from '../subgraph/types' import { splitV2o1FactoryAbi, splitV2o2FactoryAbi } from '../constants/abi' type SplitV2ABI = typeof splitV2ABI const DEFAULT_V2_VERSION = 'splitV2o2' const VALID_ERC1271_SIG = '0x1626ba7e' // TODO:add validation to execute contract function class SplitV2Transactions extends BaseTransactions { constructor(transactionClientArgs: SplitsClientConfig & TransactionConfig) { super({ supportedChainIds: SPLITS_V2_SUPPORTED_CHAIN_IDS, ...transactionClientArgs, }) } protected _getSplitV2FactoryContract( splitType: SplitV2Type, chainId: number, version: SplitV2Versions, ) { if (version === 'splitV2') { return getContract({ address: getSplitV2FactoryAddress(chainId, splitType), abi: splitV2FactoryABI, client: this._getPublicClient(chainId), }) } else if (version === 'splitV2o1') { return getContract({ address: getSplitV2o1FactoryAddress(chainId, splitType), abi: splitV2o1FactoryAbi, client: this._getPublicClient(chainId), }) } else { return getContract({ address: getSplitV2o2FactoryAddress(chainId, splitType), abi: splitV2o2FactoryAbi, client: this._getPublicClient(chainId), }) } } protected async _createSplit({ recipients, distributorFeePercent, totalAllocationPercent, splitType = SplitV2Type.Pull, ownerAddress: controllerAddress = zeroAddress, creatorAddress = zeroAddress, salt, chainId, version = DEFAULT_V2_VERSION, transactionOverrides = {}, }: CreateSplitV2Config): Promise<TransactionFormat> { const maxRecipients = splitType === SplitV2Type.Pull ? MAX_PULL_SPLIT_RECIPIENTS : MAX_PUSH_SPLIT_RECIPIENTS const { recipientAddresses, recipientAllocations, distributionIncentive, totalAllocation, } = getValidatedSplitV2Config( recipients, distributorFeePercent, totalAllocationPercent, maxRecipients, ) recipientAddresses.map((recipient) => validateAddress(recipient)) validateAddress(controllerAddress) validateAddress(creatorAddress) if (this._shouldRequireWalletClient) this._requireWalletClient() const functionChainId = this._getFunctionChainId(chainId) const functionName = salt ? 'createSplitDeterministic' : 'createSplit' const functionArgs = [ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation, distributionIncentive, }, controllerAddress, creatorAddress, ] if (salt) functionArgs.push(salt) const contract = this._getSplitV2FactoryContract( splitType, functionChainId, version, ) return this._executeContractFunction({ contractAddress: contract.address, contractAbi: contract.abi, functionName, functionArgs, transactionOverrides, }) } protected async _transferOwnership({ splitAddress, newOwner: newController, transactionOverrides = {}, }: TransferOwnershipConfig): Promise<TransactionFormat> { validateAddress(splitAddress) validateAddress(newController) if (this._shouldRequireWalletClient) this._requireWalletClient() await this._requireOwner(splitAddress) return this._executeContractFunction({ contractAddress: splitAddress, contractAbi: splitV2ABI, functionName: 'transferOwnership', functionArgs: [newController], transactionOverrides, }) } protected async _setPaused({ splitAddress, paused, transactionOverrides = {}, }: SetPausedConfig): Promise<TransactionFormat> { validateAddress(splitAddress) if (this._shouldRequireWalletClient) this._requireWalletClient() await this._requireOwner(splitAddress) return this._executeContractFunction({ contractAddress: splitAddress, contractAbi: splitV2ABI, functionName: 'setPaused', functionArgs: [paused], transactionOverrides, }) } protected async _execCalls({ splitAddress, calls, transactionOverrides = {}, }: SplitV2ExecCallsConfig): Promise<TransactionFormat> { validateAddress(splitAddress) calls.map((call) => validateAddress(call.to)) if (this._shouldRequireWalletClient) this._requireWalletClient() this._requireOwner(splitAddress) return this._executeContractFunction({ contractAddress: splitAddress, contractAbi: splitV2ABI, functionName: 'execCalls', functionArgs: [calls], transactionOverrides, }) } protected async _updateSplit({ splitAddress, recipients, distributorFeePercent, totalAllocationPercent, transactionOverrides = {}, }: UpdateSplitV2Config): Promise<TransactionFormat> { validateAddress(splitAddress) // Determine split type to apply appropriate recipient limit const functionChainId = this._getFunctionChainId(undefined) const publicClient = this._getPublicClient(functionChainId) const code = await publicClient.getCode({ address: splitAddress, }) const splitV2Type = getSplitV2TypeFromBytecode(code) const maxSplitV2Recipients = getMaxSplitV2Recipients(splitV2Type) const { recipientAddresses, recipientAllocations, distributionIncentive, totalAllocation, } = getValidatedSplitV2Config( recipients, distributorFeePercent, totalAllocationPercent, maxSplitV2Recipients, ) recipientAddresses.map((recipient) => validateAddress(recipient)) if (this._shouldRequireWalletClient) this._requireWalletClient() this._requireOwner(splitAddress) return this._executeContractFunction({ contractAddress: splitAddress, contractAbi: splitV2ABI, functionName: 'updateSplit', functionArgs: [ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation, distributionIncentive, }, ], transactionOverrides, }) } protected async _distribute({ splitAddress, tokenAddress: token, distributorAddress = this._walletClient?.account?.address as Address, chainId, splitFields, transactionOverrides = {}, }: DistributeSplitConfig): Promise<TransactionFormat> { validateAddress(splitAddress) validateAddress(token) validateAddress(distributorAddress) if (this._shouldRequireWalletClient) this._requireWalletClient() const functionChainId = this._getFunctionChainId(chainId) let split: Pick<Split, 'recipients' | 'distributorFeePercent'> if (splitFields) { split = splitFields } else if ( this._dataClient && SPLITS_SUBGRAPH_CHAIN_IDS.includes(functionChainId) ) split = await this._dataClient.getSplitMetadata({ chainId: functionChainId, splitAddress, }) else split = ( await this._getSplitMetadataViaProvider({ splitAddress, chainId: functionChainId, }) ).split const recipientAddresses = split.recipients.map( (recipient) => recipient.recipient.address, ) const recipientAllocations = split.recipients.map( (recipient) => recipient.ownership, ) return this._executeContractFunction({ contractAddress: splitAddress, contractAbi: splitV2ABI, functionName: 'distribute', functionArgs: [ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation: recipientAllocations.reduce( (acc, curr) => acc + curr, BigInt(0), ), distributionIncentive: getNumberFromPercent( split.distributorFeePercent, ), }, token === zeroAddress ? NATIVE_TOKEN_ADDRESS : token, distributorAddress, ], transactionOverrides, value: ZERO, }) } protected async _paused( splitAddress: Address, chainId: number, ): Promise<boolean> { return this._getSplitV2Contract(splitAddress, chainId).read.paused() } protected async _owner( splitAddress: Address, chainId: number, ): Promise<Address> { return this._getSplitV2Contract(splitAddress, chainId).read.owner() } protected async _checkForSplitExistence({ splitAddress, chainId, }: { splitAddress: Address chainId: number }): Promise<void> { try { await this._getSplitV2Contract(splitAddress, chainId).read.splitHash() } catch { // Split does not exist throw new AccountNotFoundError('Split', splitAddress, chainId) } } protected async _getSplitVersion({ splitAddress, chainId, }: { splitAddress: Address chainId: number }): Promise<SplitV2Versions> { try { const [, , version] = await this._getSplitV2Contract( splitAddress, chainId, ).read.eip712Domain() if (version === '2') return 'splitV2' else if (version === '2.1') return 'splitV2o1' else if (version === '2.2') return 'splitV2o2' else throw new Error('Unknown split version') } catch { // Split does not exist throw new AccountNotFoundError('Split', splitAddress, 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 }> { const formattedSplitAddress = getAddress(splitAddress) const publicClient = this._getPublicClient(chainId) const version = await this._getSplitVersion({ splitAddress, chainId }) const { blockRange, createLog, updateLog } = await getSplitCreateAndUpdateLogs<'SplitCreated', 'SplitUpdated'>({ splitAddress, publicClient, splitCreatedEvent: splitV2CreatedEvent, splitUpdatedEvent: splitV2UpdatedEvent, addresses: [ formattedSplitAddress, getSplitV2FactoryAddress(chainId, SplitV2Type.Pull), getSplitV2FactoryAddress(chainId, SplitV2Type.Push), ], startBlockNumber: getSplitV2FactoriesStartBlock(chainId), cachedBlocks: cachedData?.blocks, defaultBlockRange: cachedData?.blockRange, splitV2Version: version, }) const split = await this._buildSplitFromLogs({ splitAddress: formattedSplitAddress, chainId, createLog, updateLog, }) return { split, blockRange } } protected async _buildSplitFromLogs({ splitAddress, chainId, createLog, updateLog, }: { splitAddress: Address chainId: number createLog?: SplitV2CreatedLogType updateLog?: SplitV2UpdatedLogType }): Promise<Split> { const [owner, paused] = await Promise.all([ this._owner(splitAddress, chainId), this._paused(splitAddress, chainId), ]) if (!createLog && !updateLog) throw new Error('Split create and update logs missing') let recipients let distributorFeePercent let type: 'push' | 'pull' if (updateLog) { recipients = updateLog.args._split.recipients.map((recipient, i) => { return { recipient: { address: recipient, }, ownership: updateLog.args._split.allocations[i], percentAllocation: fromBigIntToPercent( updateLog.args._split.allocations[i], updateLog.args._split.totalAllocation, ), } }) distributorFeePercent = fromBigIntToPercent( updateLog.args._split.distributionIncentive, ) } else if (createLog) { recipients = createLog.args.splitParams.recipients.map((recipient, i) => { return { recipient: { address: recipient, }, ownership: createLog!.args.splitParams.allocations[i], percentAllocation: fromBigIntToPercent( createLog!.args.splitParams.allocations[i], createLog!.args.splitParams.totalAllocation, ), } }) distributorFeePercent = fromBigIntToPercent( createLog.args.splitParams.distributionIncentive, ) } if (createLog) type = getSplitType(chainId, createLog.address) else { const publicClient = this._getPublicClient(chainId) const code = await publicClient.getCode({ address: splitAddress, }) try { type = getSplitV2TypeFromBytecode(code) } catch { throw new Error(`failed to identify type of split ${splitAddress}`) } } const totalOwnership = recipients!.reduce((acc, recipient) => { return acc + recipient.ownership }, BigInt(0)) const split: Split = { address: splitAddress, totalOwnership, recipients: recipients!, distributorFeePercent: distributorFeePercent!, distributeDirection: type, type: 'SplitV2', controller: { address: owner, }, distributionsPaused: paused, newPotentialController: { address: zeroAddress, }, createdBlock: createLog ? Number(createLog?.blockNumber) : undefined, updateBlock: updateLog ? Number(updateLog.blockNumber) : Number(createLog!.blockNumber), } return split } protected _getSplitV2Contract( splitAddress: Address, chainId: number, ): GetContractReturnType<SplitV2ABI, SplitsPublicClient> { validateAddress(splitAddress) const publicClient = this._getPublicClient(chainId) return getContract({ address: splitAddress, abi: splitV2ABI, client: publicClient, }) } protected async _eip712Domain( splitAddress: Address, chainId: number, ): Promise<{ domain: TypedDataDomain }> { const eip712Domain = await this._getSplitV2Contract( splitAddress, chainId, ).read.eip712Domain() return { domain: { chainId: Number(eip712Domain[3].toString()), name: eip712Domain[1], version: eip712Domain[2], verifyingContract: eip712Domain[4], salt: eip712Domain[5], }, } } protected async _requireOwner(splitAddress: Address) { const ownerAddress = await this._owner( splitAddress, this._walletClient!.chain!.id, ) const walletAddress = this._walletClient!.account?.address if (ownerAddress.toLowerCase() !== walletAddress?.toLowerCase()) throw new InvalidAuthError( `Action only available to the split controller. Split id: ${splitAddress}, split controller: ${ownerAddress}, wallet address: ${walletAddress}`, ) } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class SplitV2Client extends SplitV2Transactions { readonly eventTopics: { [key: string]: Hex[] } readonly callData: SplitV2CallData readonly estimateGas: SplitV2GasEstimates readonly sign: SplitV2Signature constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.Transaction, ...clientArgs, }) const splitV2o1CreatedEvents = splitV2o1FactoryAbi.filter((abi) => { return abi.type === 'event' && abi.name === 'SplitCreated' }) const splitV2o2CreatedEvents = splitV2o2FactoryAbi.filter((abi) => { return abi.type === 'event' && abi.name === 'SplitCreated' }) this.eventTopics = { splitCreated: [ encodeEventTopics({ abi: splitV2FactoryABI, eventName: 'SplitCreated', })[0], encodeEventTopics({ abi: [splitV2o1CreatedEvents[0]], })[0], encodeEventTopics({ abi: [splitV2o1CreatedEvents[1]], })[0], encodeEventTopics({ abi: [splitV2o2CreatedEvents[0]], })[0], encodeEventTopics({ abi: [splitV2o2CreatedEvents[1]], })[0], ], splitUpdated: [ encodeEventTopics({ abi: splitV2ABI, eventName: 'SplitUpdated', })[0], ], splitDistributed: [ encodeEventTopics({ abi: splitV2ABI, eventName: 'SplitDistributed', })[0], ], ownershipTransferred: [ encodeEventTopics({ abi: splitV2ABI, eventName: 'OwnershipTransferred', })[0], ], setPaused: [ encodeEventTopics({ abi: splitV2ABI, eventName: 'SetPaused', })[0], ], execCalls: [ encodeEventTopics({ abi: splitV2ABI, eventName: 'ExecCalls', })[0], ], } this.callData = new SplitV2CallData(clientArgs) this.estimateGas = new SplitV2GasEstimates(clientArgs) this.sign = new SplitV2Signature(clientArgs) } async _submitCreateSplitTransaction( createSplitArgs: CreateSplitV2Config, ): Promise<{ txHash: Hash }> { const txHash = await this._createSplit({ ...createSplitArgs, }) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async createSplit(createSplitArgs: CreateSplitV2Config): Promise<{ splitAddress: Address event: Log }> { const { txHash } = await this._submitCreateSplitTransaction(createSplitArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.splitCreated, }) const event = events.length > 0 ? events[0] : undefined const contract = this._getSplitV2FactoryContract( createSplitArgs.splitType ?? SplitV2Type.Pull, this._chainId!, createSplitArgs.version ?? 'splitV2o2', ) if (event) { const log = decodeEventLog({ abi: contract.abi, data: event.data, topics: event.topics, }) return { splitAddress: log.args.split, event, } } throw new TransactionFailedError() } async _submitTransferOwnershipTransaction( transferOwnershipArgs: TransferOwnershipConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._transferOwnership(transferOwnershipArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async transferOwnership( transferOwnershipArgs: TransferOwnershipConfig, ): Promise<{ event: Log }> { const { txHash } = await this._submitTransferOwnershipTransaction( transferOwnershipArgs, ) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.ownershipTransferred, }) const event = events.length > 0 ? events[0] : undefined if (event) { return { event, } } throw new TransactionFailedError() } async _submitSetPauseTransaction(setPausedArgs: SetPausedConfig): Promise<{ txHash: Hash }> { const txHash = await this._setPaused(setPausedArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async setPause(setPausedArgs: SetPausedConfig): Promise<{ event: Log }> { const { txHash } = await this._submitSetPauseTransaction(setPausedArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.setPaused, }) const event = events.length > 0 ? events[0] : undefined if (event) { return { event, } } throw new TransactionFailedError() } async _submitExecCallsTransaction( execCallsArgs: SplitV2ExecCallsConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._execCalls(execCallsArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async execCalls(execCallsArgs: SplitV2ExecCallsConfig): Promise<{ event: Log }> { const { txHash } = await this._submitExecCallsTransaction(execCallsArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.execCalls, }) const event = events.length > 0 ? events[0] : undefined if (event) { return { event, } } throw new TransactionFailedError() } async _submitDistributeTransaction( distributeArgs: DistributeSplitConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._distribute(distributeArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async distribute(distributeArgs: DistributeSplitConfig): Promise<{ event: Log }> { const { txHash } = await this._submitDistributeTransaction(distributeArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.splitDistributed, }) const event = events.length > 0 ? events[0] : undefined if (event) { return { event, } } throw new TransactionFailedError() } async _submitUpdateSplitTransaction( updateSplitArgs: UpdateSplitV2Config, ): Promise<{ txHash: Hash }> { const txHash = await this._updateSplit(updateSplitArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async updateSplit(updateSplitArgs: UpdateSplitV2Config): Promise<{ event: Log }> { const { txHash } = await this._submitUpdateSplitTransaction(updateSplitArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.splitUpdated, }) const event = events.length > 0 ? events[0] : undefined if (event) { return { event, } } throw new TransactionFailedError() } private async _predictDeterministicAddress( createSplitArgs: CreateSplitV2Config, ): Promise<{ splitAddress: Address }> { if (!createSplitArgs.ownerAddress) createSplitArgs.ownerAddress = zeroAddress if (!createSplitArgs.creatorAddress) createSplitArgs.creatorAddress = zeroAddress const { recipientAddresses, recipientAllocations, distributionIncentive, totalAllocation, } = getValidatedSplitV2Config( createSplitArgs.recipients, createSplitArgs.distributorFeePercent, createSplitArgs.totalAllocationPercent, ) validateAddress(createSplitArgs.ownerAddress) validateAddress(createSplitArgs.creatorAddress) recipientAddresses.map((recipient) => validateAddress(recipient)) const functionChainId = this._getReadOnlyFunctionChainId( createSplitArgs.chainId, ) const factory = this._getSplitV2FactoryContract( createSplitArgs.splitType ?? SplitV2Type.Pull, functionChainId, createSplitArgs.version ?? DEFAULT_V2_VERSION, ) let splitAddress if (createSplitArgs.salt) { splitAddress = await factory.read.predictDeterministicAddress([ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation, distributionIncentive, }, createSplitArgs.ownerAddress, createSplitArgs.salt, ]) } else { splitAddress = await factory.read.predictDeterministicAddress([ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation, distributionIncentive: getNumberFromPercent( createSplitArgs.distributorFeePercent, ), }, createSplitArgs.ownerAddress, ]) } return { splitAddress } } async predictDeterministicAddress( createSplitArgs: CreateSplitV2Config, ): Promise<{ splitAddress: Address }> { return await this._predictDeterministicAddress(createSplitArgs) } private async _isDeployed(createSplitArgs: CreateSplitV2Config): Promise<{ splitAddress: Address deployed: boolean }> { if (!createSplitArgs.ownerAddress) createSplitArgs.ownerAddress = zeroAddress if (!createSplitArgs.creatorAddress) createSplitArgs.creatorAddress = zeroAddress const { recipientAddresses, recipientAllocations, distributionIncentive, totalAllocation, } = getValidatedSplitV2Config( createSplitArgs.recipients, createSplitArgs.distributorFeePercent, createSplitArgs.totalAllocationPercent, ) validateAddress(createSplitArgs.ownerAddress) recipientAddresses.map((recipient) => validateAddress(recipient)) const functionChainId = this._getReadOnlyFunctionChainId( createSplitArgs.chainId, ) const factory = this._getSplitV2FactoryContract( createSplitArgs.splitType ?? SplitV2Type.Pull, functionChainId, createSplitArgs.version ?? DEFAULT_V2_VERSION, ) if (!createSplitArgs.salt) throw new SaltRequired() const [splitAddress, deployed] = await factory.read.isDeployed([ { recipients: recipientAddresses, allocations: recipientAllocations, totalAllocation: totalAllocation, distributionIncentive, }, createSplitArgs.ownerAddress, createSplitArgs.salt, ]) return { splitAddress, deployed, } } async isDeployed(createSplitArgs: CreateSplitV2Config): Promise<{ splitAddress: Address deployed: boolean }> { return await this._isDeployed(createSplitArgs) } async getSplitBalance({ splitAddress, tokenAddress, chainId, }: { splitAddress: Address tokenAddress: Address chainId?: number }): Promise<{ splitBalance: bigint warehouseBalance: bigint }> { validateAddress(tokenAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitContract = this._getSplitV2Contract( splitAddress, functionChainId, ) const [splitBalance, warehouseBalance] = await splitContract.read.getSplitBalance([tokenAddress]) return { splitBalance, warehouseBalance, } } async getReplaySafeHash({ splitAddress, hash, chainId, }: { splitAddress: Address hash: Hex chainId?: number }): Promise<{ hash: Hex }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitContract = this._getSplitV2Contract( splitAddress, functionChainId, ) const replaySafeHash = await splitContract.read.replaySafeHash([hash]) return { hash: replaySafeHash, } } async isValidSignature({ splitAddress, hash, signature, chainId, }: { splitAddress: Address hash: Hex signature: Hex chainId?: number }): Promise<{ isValid: boolean }> { validateAddress(splitAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const splitContract = this._getSplitV2Contract( splitAddress, functionChainId, ) return { isValid: (await splitContract.read.isValidSignature([hash, signature])) === VALID_ERC1271_SIG, } } async eip712Domain({ splitAddress, chainId, }: { splitAddress: Address chainId?: number }): Promise<{ domain: TypedDataDomain }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) return this._eip712Domain(splitAddress, functionChainId) } async paused({ splitAddress, chainId, }: { splitAddress: Address chainId?: number }): Promise<{ paused: boolean }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) const paused = await this._paused(splitAddress, functionChainId) return { paused } } async owner({ splitAddress, chainId, }: { splitAddress: Address chainId?: number }): Promise<{ ownerAddress: Address }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) const ownerAddress = await this._owner(splitAddress, functionChainId) return { ownerAddress } } 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: SplitV2CreatedLogType updateLog?: SplitV2UpdatedLogType }) { return await this._buildSplitFromLogs({ splitAddress, chainId, createLog, updateLog, }) } async getSplitVersion({ splitAddress, chainId, }: { splitAddress: Address chainId: number }): Promise<SplitV2Versions> { return this._getSplitVersion({ splitAddress, chainId, }) } 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: 'splitV2', chainId: functionChainId, splitAddress, publicClient, fullTokenList: fullTokenList.map(getAddress), }) return { activeBalances, } } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface SplitV2Client extends BaseClientMixin {} applyMixins(SplitV2Client, [BaseClientMixin]) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class SplitV2GasEstimates extends SplitV2Transactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.GasEstimate, ...clientArgs, }) } async createSplit(createSplitArgs: CreateSplitV2Config): Promise<bigint> { const gasEstimate = await this._createSplit({ ...createSplitArgs, }) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async transferOwnership( transferOwnershipArgs: TransferOwnershipConfig, ): Promise<bigint> { const gasEstimate = await this._transferOwnership(transferOwnershipArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async setPaused(setPausedArgs: SetPausedConfig): Promise<bigint> { const gasEstimate = await this._setPaused(setPausedArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async execCalls(execCallsArgs: SplitV2ExecCallsConfig): Promise<bigint> { const gasEstimate = await this._execCalls(execCallsArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async distribute(distributeArgs: DistributeSplitConfig): Promise<bigint> { const gasEstimate = await this._distribute(distributeArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async updateSplit(updateSplitArgs: UpdateSplitV2Config): Promise<bigint> { const gasEstimate = await this._updateSplit(updateSplitArgs) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging interface SplitV2GasEstimates extends BaseGasEstimatesMixin {} applyMixins(SplitV2GasEstimates, [BaseGasEstimatesMixin]) class SplitV2CallData extends SplitV2Transactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.CallData, ...clientArgs, }) } async createSplit(createSplitArgs: CreateSplitV2Config): Promise<CallData> { const callData = await this._createSplit({ ...createSplitArgs, }) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async transferOwnership( transferOwnershipArgs: TransferOwnershipConfig, ): Promise<CallData> { const callData = await this._transferOwnership(transferOwnershipArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async setPaused(setPausedArgs: SetPausedConfig): Promise<CallData> { const callData = await this._setPaused(setPausedArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async execCalls(execCallsArgs: SplitV2ExecCallsConfig): Promise<CallData> { const callData = await this._execCalls(execCallsArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async distribute(distributeArgs: DistributeSplitConfig): Promise<CallData> { const callData = await this._distribute(distributeArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async updateSplit(updateSplitArgs: UpdateSplitV2Config): Promise<CallData> { const callData = await this._updateSplit(updateSplitArgs) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } } class SplitV2Signature extends SplitV2Transactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.Signature, ...clientArgs, }) } async signData( splitAddress: Address, data: Hex, chainId?: number, ): Promise<{ signature: Hex }> { const functionChainId = this._getReadOnlyFunctionChainId(chainId) const { domain } = await this._eip712Domain(splitAddress, functionChainId) this._requireWalletClient() const signature = await this._walletClient?.signTypedData({ account: this._walletClient!.account!, domain, types: SigTypes, primaryType: 'SplitWalletMessage', message: { hash: data, }, }) if (!signature) throw new Error('Error in signing data') return { signature, } } } const SigTypes = { SplitWalletMessage: [ { name: 'hash', type: 'bytes32', }, ], }