@0xsplits/splits-sdk
Version:
SDK for the 0xSplits protocol
949 lines (841 loc) • 27.6 kB
text/typescript
import { Client } from '@urql/core'
import { DocumentNode } from 'graphql'
import { Address, getAddress, zeroAddress } from 'viem'
import { SPLITS_SUBGRAPH_CHAIN_IDS } from '../constants'
import {
DataClientConfig,
FormattedContractEarnings,
FormattedEarningsByContract,
FormattedSplitEarnings,
FormattedTokenBalances,
FormattedUserEarningsByContract,
LiquidSplit,
Split,
SplitsContract,
SplitsPublicClient,
Swapper,
VestingModule,
WaterfallModule,
} from '../types'
import {
AccountNotFoundError,
InvalidArgumentError,
InvalidConfigError,
MissingPublicClientError,
UnsupportedSubgraphChainIdError,
} from '../errors'
import {
ACCOUNT_QUERY,
FULL_ACCOUNT_QUERY,
GqlVariables,
formatFullGqlAccount,
formatGqlAccount,
getGraphqlClient,
} from '../subgraph'
import {
GqlAccount,
GqlLiquidSplit,
GqlPassThroughWallet,
GqlSplit,
GqlSwapper,
GqlVestingModule,
GqlWaterfallModule,
IAccountType,
ILiquidSplit,
ISplit,
ISubgraphAccount,
ISwapper,
IVestingModule,
IWaterfallModule,
} from '../subgraph/types'
import { MAX_RELATED_ACCOUNTS } from '../subgraph/constants'
import { formatGqlSplit, protectedFormatSplit } from '../subgraph/split'
import {
formatGqlWaterfallModule,
protectedFormatWaterfallModule,
} from '../subgraph/waterfall'
import {
formatGqlVestingModule,
protectedFormatVestingModule,
} from '../subgraph/vesting'
import { formatGqlSwapper, protectedFormatSwapper } from '../subgraph/swapper'
import { formatGqlPassThroughWallet } from '../subgraph/pass-through-wallet'
import {
formatGqlLiquidSplit,
protectedFormatLiquidSplit,
} from '../subgraph/liquid'
import { formatAccountBalances, formatContractEarnings } from '../subgraph/user'
import {
addEnsNames,
fetchActiveBalances,
fetchContractBalancesWithAlchemy,
fetchERC20TransferredTokens,
fromBigIntToTokenValue,
getTokenData,
isAlchemyPublicClient,
isLogsPublicClient,
mergeFormattedTokenBalances,
validateAddress,
} from '../utils'
export class DataClient {
readonly _ensPublicClient: SplitsPublicClient | undefined // DEPRECATED
readonly _publicClient: SplitsPublicClient | undefined // DEPRECATED
readonly _publicClients:
| {
[chainId: number]: SplitsPublicClient
}
| undefined
private readonly _graphqlClient: Client | undefined
readonly _includeEnsNames: boolean
constructor({
publicClient,
publicClients,
ensPublicClient,
apiConfig,
includeEnsNames = false,
}: DataClientConfig) {
if (
includeEnsNames &&
!publicClient &&
!publicClients?.[1] &&
!ensPublicClient
)
throw new InvalidConfigError(
'Must include a mainnet public client if includeEnsNames is set to true',
)
this._ensPublicClient =
publicClients?.[1] ?? ensPublicClient ?? publicClient
this._publicClient = publicClient
this._publicClients = publicClients
this._includeEnsNames = includeEnsNames
this._graphqlClient = getGraphqlClient(apiConfig)
}
protected _requirePublicClient(chainId: number) {
this._getPublicClient(chainId)
}
protected _getPublicClient(chainId: number): SplitsPublicClient {
if (this._publicClients && this._publicClients[chainId]) {
return this._publicClients[chainId]
}
if (!this._publicClient)
throw new MissingPublicClientError(
`Public client required on chain ${chainId} to perform this action, please update your call to the constructor`,
)
return this._publicClient
}
protected async _makeGqlRequest<ResponseType>(
query: DocumentNode,
variables?: GqlVariables,
): Promise<ResponseType> {
if (!this._graphqlClient) {
throw new UnsupportedSubgraphChainIdError()
}
if (variables?.chainId && typeof variables.chainId === 'string') {
if (!SPLITS_SUBGRAPH_CHAIN_IDS.includes(Number(variables.chainId))) {
throw new UnsupportedSubgraphChainIdError()
}
}
const response = await this._graphqlClient
.query(query, variables)
.toPromise()
if (response.error) {
throw response.error
}
return response.data
}
protected async _loadAccount(
accountId: string,
chainId: number,
): Promise<IAccountType | undefined> {
const result = await this._makeGqlRequest<{
account: GqlAccount
}>(ACCOUNT_QUERY, {
accountId: accountId.toLowerCase(),
chainId: chainId.toString(),
})
if (!result.account) return
return formatGqlAccount(result.account)
}
protected async _loadFullAccount(
accountId: string,
chainId: number,
): Promise<ISubgraphAccount> {
const result = await this._makeGqlRequest<{
account: GqlAccount
relatedAccounts: {
controllingSplits: GqlSplit[]
pendingControlSplits: GqlSplit[]
ownedSwappers: GqlSwapper[]
ownedPassThroughWallets: GqlPassThroughWallet[]
upstreamSplits: GqlSplit[]
upstreamLiquidSplits: GqlLiquidSplit[]
upstreamWaterfalls: GqlWaterfallModule[]
upstreamVesting: GqlVestingModule[]
upstreamSwappers: GqlSwapper[]
upstreamPassThroughWallets: GqlPassThroughWallet[]
}
}>(FULL_ACCOUNT_QUERY, {
accountId: accountId.toLowerCase(),
chainId: chainId.toString(),
relatedAccountsLimit: MAX_RELATED_ACCOUNTS,
})
const response: ISubgraphAccount = {}
const relatedAccounts = result.relatedAccounts
if (result.account) {
response.upstreamSplits =
relatedAccounts.upstreamSplits?.map((gqlSplit) =>
formatGqlSplit(gqlSplit),
) ?? []
response.upstreamWaterfalls =
relatedAccounts.upstreamWaterfalls?.map((waterfallModule) =>
formatGqlWaterfallModule(waterfallModule),
) ?? []
response.upstreamVesting =
relatedAccounts.upstreamVesting?.map((vestingModule) =>
formatGqlVestingModule(vestingModule),
) ?? []
response.upstreamSwappers =
relatedAccounts.upstreamSwappers?.map((swapper) =>
formatGqlSwapper(swapper),
) ?? []
response.upstreamPassThroughWallets =
relatedAccounts.upstreamPassThroughWallets?.map((passThroughWallet) =>
formatGqlPassThroughWallet(passThroughWallet),
) ?? []
response.upstreamLiquidSplits =
relatedAccounts.upstreamLiquidSplits?.map((gqlLiquidSplit) =>
formatGqlLiquidSplit(gqlLiquidSplit),
) ?? []
if (response.upstreamLiquidSplits.length > 0) {
response.upstreamSplits = response.upstreamSplits.concat(
relatedAccounts.upstreamLiquidSplits.map((gqlLiquidSplit) =>
formatGqlSplit(gqlLiquidSplit.split),
),
)
}
response.controllingSplits =
relatedAccounts.controllingSplits?.map((split) =>
formatGqlSplit(split),
) ?? []
response.pendingControlSplits =
relatedAccounts.pendingControlSplits?.map((split) =>
formatGqlSplit(split),
) ?? []
response.ownedSwappers =
relatedAccounts.ownedSwappers?.map((swapper) =>
formatGqlSwapper(swapper),
) ?? []
response.ownedPassThroughWallets =
relatedAccounts.ownedPassThroughWallets?.map((passThroughWallet) =>
formatGqlPassThroughWallet(passThroughWallet),
) ?? []
response.account = formatFullGqlAccount(
result.account,
response.upstreamSplits,
response.upstreamVesting,
response.upstreamWaterfalls,
response.upstreamLiquidSplits,
response.upstreamSwappers,
response.upstreamPassThroughWallets,
relatedAccounts.controllingSplits,
relatedAccounts.pendingControlSplits,
relatedAccounts.ownedSwappers,
relatedAccounts.ownedPassThroughWallets,
)
const allPassThroughWallets = [...relatedAccounts.ownedPassThroughWallets]
if (result.account.__typename === 'PassThroughWallet') {
allPassThroughWallets.push(result.account)
}
if (result.account.__typename === 'LiquidSplit') {
// Not really an upstream split, but it's just used for loading. Should probably update that name. Maybe related splits?
response.upstreamSplits.push(formatGqlSplit(result.account.split))
} else if (result.account.__typename === 'WaterfallModule') {
result.account.tranches.map((gqlWaterfallTranche) => {
if (gqlWaterfallTranche.recipient.__typename === 'Split') {
response.upstreamSplits = response.upstreamSplits ?? []
response.upstreamSplits.push(
formatGqlSplit(gqlWaterfallTranche.recipient),
)
}
})
}
if (allPassThroughWallets.length > 0) {
allPassThroughWallets.map((gqlPassThroughWallet) => {
if (gqlPassThroughWallet.passThroughAccount.__typename === 'Split') {
response.upstreamSplits = response.upstreamSplits ?? []
response.upstreamSplits.push(
formatGqlSplit(gqlPassThroughWallet.passThroughAccount),
)
gqlPassThroughWallet.passThroughAccount.recipients.map(
(gqlRecipient) => {
if (gqlRecipient.account.__typename === 'Swapper') {
response.upstreamSwappers = response.upstreamSwappers ?? []
response.upstreamSwappers.push(
formatGqlSwapper(gqlRecipient.account),
)
}
},
)
}
})
}
}
return response
}
protected async _getUserBalancesByContract({
chainId,
userAddress,
contractAddresses,
}: {
chainId: number
userAddress: string
contractAddresses?: string[]
}): Promise<{
contractEarnings: FormattedEarningsByContract
}> {
const response = await this._loadAccount(userAddress, chainId)
if (!response) throw new AccountNotFoundError('user', userAddress, chainId)
const contractEarnings = formatContractEarnings(
response.contractEarnings,
contractAddresses,
)
return {
contractEarnings,
}
}
protected async _getAccountBalances({
chainId,
accountAddress,
includeActiveBalances,
erc20TokenList,
}: {
chainId: number
accountAddress: Address
includeActiveBalances: boolean
erc20TokenList?: string[]
}): Promise<{
withdrawn: FormattedTokenBalances
distributed: FormattedTokenBalances
activeBalances?: FormattedTokenBalances
}> {
const functionPublicClient = this._getPublicClient(chainId)
const response = await this._loadAccount(accountAddress, chainId)
if (!response)
throw new AccountNotFoundError('account', accountAddress, chainId)
const withdrawn =
response.type === 'user' ? formatAccountBalances(response.withdrawn) : {}
const distributed = formatAccountBalances(response.distributions)
if (!includeActiveBalances) {
return {
withdrawn,
distributed,
}
}
const splitmainBalances = formatAccountBalances(response.splitmainBalances)
const warehouseBalances = formatAccountBalances(response.warehouseBalances)
if (response.type === 'user') {
return {
withdrawn,
distributed,
activeBalances: mergeFormattedTokenBalances([
splitmainBalances,
warehouseBalances,
]),
}
}
// Need to fetch current balance. Handle alchemy/infura with logs, and all other rpc's with token list
if (!functionPublicClient)
throw new MissingPublicClientError(
'Public client required to get active balances. Please update your call to the client constructor, or set includeActiveBalances to false',
)
if (functionPublicClient.chain?.id !== chainId) {
throw new InvalidArgumentError(
`Public client is set to chain id ${functionPublicClient.chain?.id}, but active balances are being fetched on chain ${chainId}. Active balances can only be fetched on the same chain as the public client.`,
)
}
const tokenList = erc20TokenList ?? []
let balances: FormattedTokenBalances
if (
erc20TokenList === undefined &&
isAlchemyPublicClient(functionPublicClient)
) {
// If no token list passed in and we're using alchemy, fetch all balances with alchemy's custom api
balances = await fetchContractBalancesWithAlchemy(
chainId,
accountAddress,
functionPublicClient,
)
} else {
if (erc20TokenList === undefined) {
// If no token list passed in, make sure the public client supports logs and then fetch all erc20 tokens
if (!isLogsPublicClient(functionPublicClient))
throw new InvalidArgumentError(
'Token list required if public client is not alchemy or infura',
)
const transferredErc20Tokens = await fetchERC20TransferredTokens(
chainId,
functionPublicClient,
accountAddress,
)
tokenList.push(...transferredErc20Tokens)
}
// Include already distributed tokens in list for balances
const customTokens = Object.keys(distributed) ?? []
const fullTokenList = Array.from(
new Set(
[zeroAddress, ...tokenList]
.concat(Object.keys(splitmainBalances))
.concat(Object.keys(warehouseBalances))
.concat(customTokens)
.map((token) => getAddress(token)),
),
)
balances = await fetchActiveBalances(
chainId,
accountAddress,
functionPublicClient,
fullTokenList,
)
}
const allTokens = Array.from(
new Set(
Object.keys(balances)
.concat(Object.keys(splitmainBalances))
.concat(Object.keys(warehouseBalances)),
),
)
const filteredBalances = allTokens.reduce((acc, token) => {
const splitmainBalanceAmount =
splitmainBalances[token]?.rawAmount ?? BigInt(0)
const warehouseBalanceAmount =
warehouseBalances[token]?.rawAmount ?? BigInt(0)
const contractBalanceAmount = balances[token]?.rawAmount ?? BigInt(0)
// SplitMain leaves a balance of 1 for gas efficiency in internal balances.
// Splits leave a balance of 1 (for erc20) for gas efficiency
const tokenBalance =
(splitmainBalanceAmount > BigInt(1)
? splitmainBalanceAmount
: BigInt(0)) +
(warehouseBalanceAmount > BigInt(1)
? warehouseBalanceAmount
: BigInt(0)) +
(contractBalanceAmount > BigInt(1) ? contractBalanceAmount : BigInt(0))
const symbol =
splitmainBalances[token]?.symbol ??
warehouseBalances[token]?.symbol ??
balances[token]?.symbol
const decimals =
splitmainBalances[token]?.decimals ??
warehouseBalances[token]?.decimals ??
balances[token]?.decimals
const formattedAmount = fromBigIntToTokenValue(tokenBalance, decimals)
if (tokenBalance > BigInt(0))
acc[token] = {
rawAmount: tokenBalance,
formattedAmount,
symbol,
decimals,
}
return acc
}, {} as FormattedTokenBalances)
return {
withdrawn,
distributed,
activeBalances: filteredBalances,
}
}
async getContractEarnings({
chainId,
contractAddress,
includeActiveBalances = true,
erc20TokenList,
}: {
chainId: number
contractAddress: string
includeActiveBalances?: boolean
erc20TokenList?: string[]
}): Promise<FormattedContractEarnings> {
validateAddress(contractAddress)
if (includeActiveBalances) this._requirePublicClient(chainId)
const { distributed, activeBalances } = await this._getAccountBalances({
chainId,
accountAddress: getAddress(contractAddress),
includeActiveBalances,
erc20TokenList,
})
if (!includeActiveBalances) return { distributed }
return { distributed, activeBalances }
}
async getSplitMetadata({
chainId,
splitAddress,
}: {
chainId: number
splitAddress: string
}): Promise<Split> {
validateAddress(splitAddress)
const response = await this._loadAccount(splitAddress, chainId)
if (
!response ||
(response.type !== 'split' &&
response.type !== 'splitV2' &&
response.type !== 'splitV2o1')
)
throw new AccountNotFoundError('split', splitAddress, chainId)
return await this.formatSplit(response)
}
async getAccountMetadata({
chainId,
accountAddress,
}: {
chainId: number
accountAddress: string
}): Promise<SplitsContract | undefined> {
validateAddress(accountAddress)
this._requirePublicClient(chainId)
const response = await this._loadAccount(accountAddress, chainId)
if (!response)
throw new AccountNotFoundError('account', accountAddress, chainId)
return await this._formatAccount(chainId, response)
}
// Graphql read actions
async getRelatedSplits({
chainId,
address,
}: {
chainId: number
address: string
}): Promise<{
receivingFrom: Split[]
controlling: Split[]
pendingControl: Split[]
}> {
validateAddress(address)
const response = await this._loadFullAccount(address, chainId)
const [receivingFrom, controlling, pendingControl] = await Promise.all([
Promise.all(
response.upstreamSplits
? response.upstreamSplits.map(async (recipient) =>
this.formatSplit(recipient),
)
: [],
),
Promise.all(
response.controllingSplits
? response.controllingSplits.map(async (recipient) =>
this.formatSplit(recipient),
)
: [],
),
Promise.all(
response.pendingControlSplits
? response.pendingControlSplits.map(async (recipient) =>
this.formatSplit(recipient),
)
: [],
),
])
return {
receivingFrom,
controlling,
pendingControl,
}
}
async getSplitEarnings({
chainId,
splitAddress,
includeActiveBalances = true,
erc20TokenList,
}: {
chainId: number
splitAddress: string
includeActiveBalances?: boolean
erc20TokenList?: string[]
}): Promise<FormattedSplitEarnings> {
validateAddress(splitAddress)
if (includeActiveBalances) this._requirePublicClient(chainId)
const { distributed, activeBalances } = await this._getAccountBalances({
chainId,
accountAddress: getAddress(splitAddress),
includeActiveBalances,
erc20TokenList,
})
if (!includeActiveBalances) return { distributed }
return { distributed, activeBalances }
}
async getUserEarnings({
chainId,
userAddress,
}: {
chainId: number
userAddress: string
}): Promise<{
withdrawn: FormattedTokenBalances
activeBalances: FormattedTokenBalances
}> {
validateAddress(userAddress)
const { withdrawn, activeBalances } = await this._getAccountBalances({
chainId,
accountAddress: getAddress(userAddress),
includeActiveBalances: true,
})
if (!activeBalances) throw new Error('Missing active balances')
return { withdrawn, activeBalances }
}
async getUserEarningsByContract({
chainId,
userAddress,
contractAddresses,
}: {
chainId: number
userAddress: string
contractAddresses?: string[]
}): Promise<FormattedUserEarningsByContract> {
validateAddress(userAddress)
if (contractAddresses) {
contractAddresses.map((contractAddress) =>
validateAddress(contractAddress),
)
}
const { contractEarnings } = await this._getUserBalancesByContract({
chainId,
userAddress,
contractAddresses,
})
const [withdrawn, activeBalances] = Object.values(contractEarnings).reduce(
(
acc,
{
withdrawn: contractWithdrawn,
activeBalances: contractActiveBalances,
},
) => {
Object.keys(contractWithdrawn).map((tokenId) => {
if (!acc[0][tokenId])
acc[0][tokenId] = {
symbol: contractWithdrawn[tokenId].symbol,
decimals: contractWithdrawn[tokenId].decimals,
rawAmount: BigInt(0),
formattedAmount: '0',
}
const rawAmount =
acc[0][tokenId].rawAmount + contractWithdrawn[tokenId].rawAmount
const formattedAmount = fromBigIntToTokenValue(
rawAmount,
contractWithdrawn[tokenId].decimals,
)
acc[0][tokenId].rawAmount = rawAmount
acc[0][tokenId].formattedAmount = formattedAmount
})
Object.keys(contractActiveBalances).map((tokenId) => {
if (!acc[1][tokenId])
acc[1][tokenId] = {
symbol: contractActiveBalances[tokenId].symbol,
decimals: contractActiveBalances[tokenId].decimals,
rawAmount: BigInt(0),
formattedAmount: '0',
}
const rawAmount =
acc[1][tokenId].rawAmount +
contractActiveBalances[tokenId].rawAmount
const formattedAmount = fromBigIntToTokenValue(
rawAmount,
contractActiveBalances[tokenId].decimals,
)
acc[1][tokenId].rawAmount = rawAmount
acc[1][tokenId].formattedAmount = formattedAmount
})
return acc
},
[{} as FormattedTokenBalances, {} as FormattedTokenBalances],
)
return {
withdrawn,
activeBalances,
earningsByContract: contractEarnings,
}
}
async getLiquidSplitMetadata({
chainId,
liquidSplitAddress,
}: {
chainId: number
liquidSplitAddress: string
}): Promise<LiquidSplit> {
validateAddress(liquidSplitAddress)
const response = await this._loadAccount(liquidSplitAddress, chainId)
if (!response || response.type !== 'liquidSplit')
throw new AccountNotFoundError(
'liquid split',
liquidSplitAddress,
chainId,
)
return await this.formatLiquidSplit(chainId, response)
}
async getSwapperMetadata({
chainId,
swapperAddress,
}: {
chainId: number
swapperAddress: string
}): Promise<Swapper> {
validateAddress(swapperAddress)
const response = await this._loadAccount(swapperAddress, chainId)
if (!response || response.type !== 'swapper')
throw new AccountNotFoundError('swapper', swapperAddress, chainId)
return await this.formatSwapper(response)
}
async getVestingMetadata({
chainId,
vestingModuleAddress,
}: {
chainId: number
vestingModuleAddress: string
}): Promise<VestingModule> {
validateAddress(vestingModuleAddress)
const response = await this._loadAccount(vestingModuleAddress, chainId)
if (!response || response.type !== 'vesting')
throw new AccountNotFoundError(
'vesting module',
vestingModuleAddress,
chainId,
)
return await this.formatVestingModule(chainId, response)
}
async getWaterfallMetadata({
chainId,
waterfallModuleAddress,
}: {
chainId: number
waterfallModuleAddress: string
}): Promise<WaterfallModule> {
validateAddress(waterfallModuleAddress)
const response = await this._loadAccount(waterfallModuleAddress, chainId)
if (!response || response.type != 'waterfall')
throw new AccountNotFoundError(
'waterfall module',
waterfallModuleAddress,
chainId,
)
return await this.formatWaterfallModule(chainId, response)
}
// Helper functions
private async _formatAccount(
chainId: number,
gqlAccount: IAccountType,
): Promise<SplitsContract | undefined> {
if (!gqlAccount) return
if (gqlAccount.type === 'split') return await this.formatSplit(gqlAccount)
else if (gqlAccount.type === 'waterfall')
return await this.formatWaterfallModule(chainId, gqlAccount)
else if (gqlAccount.type === 'liquidSplit')
return await this.formatLiquidSplit(chainId, gqlAccount)
else if (gqlAccount.type === 'swapper')
return await this.formatSwapper(gqlAccount)
}
private async formatWaterfallModule(
chainId: number,
gqlWaterfallModule: IWaterfallModule,
): Promise<WaterfallModule> {
this._requirePublicClient(chainId)
const publicClient = this._getPublicClient(chainId)
const tokenData = await getTokenData(
chainId,
getAddress(gqlWaterfallModule.token),
publicClient,
)
const waterfallModule = protectedFormatWaterfallModule(
gqlWaterfallModule,
tokenData.symbol,
tokenData.decimals,
)
if (this._includeEnsNames) {
const ensRecipients = waterfallModule.tranches
.map((tranche) => {
return tranche.recipient
})
.concat(
waterfallModule.nonWaterfallRecipient
? [waterfallModule.nonWaterfallRecipient]
: [],
)
await addEnsNames(this._ensPublicClient ?? publicClient, ensRecipients)
}
return waterfallModule
}
private async formatVestingModule(
chainId: number,
gqlVestingModule: IVestingModule,
): Promise<VestingModule> {
this._requirePublicClient(chainId)
const publicClient = this._getPublicClient(chainId)
const tokenIds = Array.from(
new Set(gqlVestingModule.streams?.map((stream) => stream.token) ?? []),
)
const tokenData: { [token: string]: { symbol: string; decimals: number } } =
{}
await Promise.all(
tokenIds.map(async (token) => {
const result = await getTokenData(
chainId,
getAddress(token),
publicClient,
)
tokenData[token] = result
}),
)
const vestingModule = protectedFormatVestingModule(
gqlVestingModule,
tokenData,
)
if (this._includeEnsNames) {
await addEnsNames(this._ensPublicClient ?? publicClient, [
vestingModule.beneficiary,
])
}
return vestingModule
}
private async formatSwapper(gqlSwapper: ISwapper): Promise<Swapper> {
const swapper = protectedFormatSwapper(gqlSwapper)
if (this._includeEnsNames) {
if (!this._ensPublicClient) throw new Error()
const ensRecipients = [swapper.beneficiary].concat(
swapper.owner ? [swapper.owner] : [],
)
await addEnsNames(this._ensPublicClient, ensRecipients)
}
return swapper
}
private async formatLiquidSplit(
chainId: number,
gqlLiquidSplit: ILiquidSplit,
): Promise<LiquidSplit> {
this._requirePublicClient(chainId)
const liquidSplit = protectedFormatLiquidSplit(gqlLiquidSplit)
if (this._includeEnsNames) {
await addEnsNames(
this._ensPublicClient!,
liquidSplit.holders.map((holder) => {
return holder.recipient
}),
)
}
return liquidSplit
}
private async formatSplit(gqlSplit: ISplit): Promise<Split> {
const split = protectedFormatSplit(gqlSplit)
if (this._includeEnsNames) {
if (!this._ensPublicClient) throw new Error()
const ensRecipients = split.recipients
.map((recipient) => {
return recipient.recipient
})
.concat(split.controller ? [split.controller] : [])
.concat(
split.newPotentialController ? [split.newPotentialController] : [],
)
await addEnsNames(this._ensPublicClient, ensRecipients)
}
return split
}
}