@0xsplits/splits-sdk
Version:
SDK for the 0xSplits protocol
1,622 lines (1,407 loc) • 46.1 kB
text/typescript
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
}
}