UNPKG

@0xsplits/splits-sdk

Version:

SDK for the 0xSplits protocol

527 lines (450 loc) 15.4 kB
import { Address, GetContractReturnType, Hash, Hex, Log, decodeEventLog, encodeEventTopics, getAddress, getContract, } from 'viem' import { BaseClientMixin, BaseGasEstimatesMixin, BaseTransactions, } from './base' import { TransactionType, PASS_THROUGH_WALLET_CHAIN_IDS, getPassThroughWalletFactoryAddress, } from '../constants' import { passThroughWalletFactoryAbi } from '../constants/abi/passThroughWalletFactory' import { passThroughWalletAbi } from '../constants/abi/passThroughWallet' import { InvalidAuthError, TransactionFailedError } from '../errors' import { applyMixins } from './mixin' import type { CallData, CreatePassThroughWalletConfig, PassThroughTokensConfig, PassThroughWalletExecCallsConfig, PassThroughWalletPauseConfig, SetPassThroughConfig, SplitsClientConfig, SplitsPublicClient, TransactionConfig, TransactionFormat, } from '../types' import { validateAddress } from '../utils/validation' type PassThroughWalletAbi = typeof passThroughWalletAbi class PassThroughWalletTransactions extends BaseTransactions { constructor(transactionClientArgs: SplitsClientConfig & TransactionConfig) { super({ supportedChainIds: PASS_THROUGH_WALLET_CHAIN_IDS, ...transactionClientArgs, }) } protected async _createPassThroughWalletTransaction({ owner, paused = false, passThrough, chainId, transactionOverrides = {}, }: CreatePassThroughWalletConfig): Promise<TransactionFormat> { validateAddress(owner) validateAddress(passThrough) if (this._shouldRequireWalletClient) this._requireWalletClient() const functionChainId = this._getFunctionChainId(chainId) const result = await this._executeContractFunction({ contractAddress: getPassThroughWalletFactoryAddress(functionChainId), contractAbi: passThroughWalletFactoryAbi, functionName: 'createPassThroughWallet', functionArgs: [[owner, paused, passThrough]], transactionOverrides, }) return result } protected async _passThroughTokensTransaction({ passThroughWalletAddress, tokens, transactionOverrides = {}, }: PassThroughTokensConfig): Promise<TransactionFormat> { validateAddress(passThroughWalletAddress) tokens.map((token) => validateAddress(token)) if (this._shouldRequireWalletClient) this._requireWalletClient() const result = await this._executeContractFunction({ contractAddress: getAddress(passThroughWalletAddress), contractAbi: passThroughWalletAbi, functionName: 'passThroughTokens', functionArgs: [tokens], transactionOverrides, }) return result } protected async _setPassThroughTransaction({ passThroughWalletAddress, passThrough, transactionOverrides = {}, }: SetPassThroughConfig): Promise<TransactionFormat> { validateAddress(passThroughWalletAddress) validateAddress(passThrough) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireOwner(passThroughWalletAddress) } const result = await this._executeContractFunction({ contractAddress: getAddress(passThroughWalletAddress), contractAbi: passThroughWalletAbi, functionName: 'setPassThrough', functionArgs: [passThrough], transactionOverrides, }) return result } protected async _setPausedTransaction({ passThroughWalletAddress, paused, transactionOverrides = {}, }: PassThroughWalletPauseConfig): Promise<TransactionFormat> { validateAddress(passThroughWalletAddress) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireOwner(passThroughWalletAddress) } const result = await this._executeContractFunction({ contractAddress: getAddress(passThroughWalletAddress), contractAbi: passThroughWalletAbi, functionName: 'setPaused', functionArgs: [paused], transactionOverrides, }) return result } protected async _execCallsTransaction({ passThroughWalletAddress, calls, transactionOverrides = {}, }: PassThroughWalletExecCallsConfig): Promise<TransactionFormat> { validateAddress(passThroughWalletAddress) calls.map((callData) => validateAddress(callData.to)) if (this._shouldRequireWalletClient) { this._requireWalletClient() await this._requireOwner(passThroughWalletAddress) } const formattedCalls = calls.map((callData) => { return [callData.to, callData.value, callData.data] }) const result = await this._executeContractFunction({ contractAddress: getAddress(passThroughWalletAddress), contractAbi: passThroughWalletAbi, functionName: 'execCalls', functionArgs: [formattedCalls], transactionOverrides, }) return result } private async _requireOwner(passThroughWalletAddress: string) { this._requireWalletClient() const walletAddress = this._walletClient!.account?.address const passThroughWalletContract = this._getPassThroughWalletContract( passThroughWalletAddress, this._walletClient!.chain!.id, ) const owner = await passThroughWalletContract.read.owner() if (owner !== walletAddress) throw new InvalidAuthError( `Action only available to the pass through wallet owner. Pass through wallet address: ${passThroughWalletAddress}, owner: ${owner}, wallet address: ${walletAddress}`, ) } protected _getPassThroughWalletContract( passThroughWallet: string, chainId: number, ): GetContractReturnType<PassThroughWalletAbi, SplitsPublicClient> { const publicClient = this._getPublicClient(chainId) return getContract({ address: getAddress(passThroughWallet), abi: passThroughWalletAbi, client: publicClient, }) } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class PassThroughWalletClient extends PassThroughWalletTransactions { readonly eventTopics: { [key: string]: Hex[] } readonly callData: PassThroughWalletCallData readonly estimateGas: PassThroughWalletGasEstimates constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.Transaction, ...clientArgs, }) this.eventTopics = { createPassThroughWallet: [ encodeEventTopics({ abi: passThroughWalletFactoryAbi, eventName: 'CreatePassThroughWallet', })[0], ], passThroughTokens: [ encodeEventTopics({ abi: passThroughWalletAbi, eventName: 'PassThrough', })[0], ], setPassThrough: [ encodeEventTopics({ abi: passThroughWalletAbi, eventName: 'SetPassThrough', })[0], ], setPaused: [ encodeEventTopics({ abi: passThroughWalletAbi, eventName: 'SetPaused', })[0], ], execCalls: [ encodeEventTopics({ abi: passThroughWalletAbi, eventName: 'ExecCalls', })[0], ], } this.callData = new PassThroughWalletCallData(clientArgs) this.estimateGas = new PassThroughWalletGasEstimates(clientArgs) } // Write actions async _submitCreatePassThroughWalletTransaction( createPassThroughArgs: CreatePassThroughWalletConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._createPassThroughWalletTransaction( createPassThroughArgs, ) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async createPassThroughWallet( createPassThroughArgs: CreatePassThroughWalletConfig, ): Promise<{ passThroughWalletAddress: Address event: Log }> { const { txHash } = await this._submitCreatePassThroughWalletTransaction( createPassThroughArgs, ) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.createPassThroughWallet, }) const event = events.length > 0 ? events[0] : undefined if (event) { const log = decodeEventLog({ abi: passThroughWalletFactoryAbi, data: event.data, topics: event.topics, }) return { passThroughWalletAddress: log.args.passThroughWallet, event, } } throw new TransactionFailedError() } async _submitPassThroughTokensTransaction( passThroughArgs: PassThroughTokensConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._passThroughTokensTransaction(passThroughArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async passThroughTokens(passThroughArgs: PassThroughTokensConfig): Promise<{ event: Log }> { const { txHash } = await this._submitPassThroughTokensTransaction(passThroughArgs) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.passThroughTokens, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event, } throw new TransactionFailedError() } async _submitSetPassThroughTransaction(args: SetPassThroughConfig): Promise<{ txHash: Hash }> { const txHash = await this._setPassThroughTransaction(args) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async setPassThrough(args: SetPassThroughConfig): Promise<{ event: Log }> { const { txHash } = await this._submitSetPassThroughTransaction(args) const events = await this.getTransactionEvents({ txHash, eventTopics: this.eventTopics.setPassThrough, }) const event = events.length > 0 ? events[0] : undefined if (event) return { event, } throw new TransactionFailedError() } async _submitSetPausedTransaction( pauseArgs: PassThroughWalletPauseConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._setPausedTransaction(pauseArgs) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async setPaused(pauseArgs: PassThroughWalletPauseConfig): Promise<{ event: Log }> { const { txHash } = await this._submitSetPausedTransaction(pauseArgs) 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( args: PassThroughWalletExecCallsConfig, ): Promise<{ txHash: Hash }> { const txHash = await this._execCallsTransaction(args) if (!this._isContractTransaction(txHash)) throw new Error('Invalid response') return { txHash } } async execCalls(args: PassThroughWalletExecCallsConfig): Promise<{ event: Log }> { const { txHash } = await this._submitExecCallsTransaction(args) 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() } // Read actions async getPassThrough({ passThroughWalletAddress, chainId, }: { passThroughWalletAddress: string chainId?: number }): Promise<{ passThrough: string }> { validateAddress(passThroughWalletAddress) const functionChainId = this._getReadOnlyFunctionChainId(chainId) const passThroughWalletContract = this._getPassThroughWalletContract( passThroughWalletAddress, functionChainId, ) const passThrough = await passThroughWalletContract.read.passThrough() return { passThrough, } } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface PassThroughWalletClient extends BaseClientMixin {} applyMixins(PassThroughWalletClient, [BaseClientMixin]) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging class PassThroughWalletGasEstimates extends PassThroughWalletTransactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.GasEstimate, ...clientArgs, }) } async createPassThroughWallet( args: CreatePassThroughWalletConfig, ): Promise<bigint> { const gasEstimate = await this._createPassThroughWalletTransaction(args) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async passThroughTokens(args: PassThroughTokensConfig): Promise<bigint> { const gasEstimate = await this._passThroughTokensTransaction(args) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async setPassThrough(args: SetPassThroughConfig): Promise<bigint> { const gasEstimate = await this._setPassThroughTransaction(args) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async setPaused(args: PassThroughWalletPauseConfig): Promise<bigint> { const gasEstimate = await this._setPausedTransaction(args) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } async execCalls(args: PassThroughWalletExecCallsConfig): Promise<bigint> { const gasEstimate = await this._execCallsTransaction(args) if (!this._isBigInt(gasEstimate)) throw new Error('Invalid response') return gasEstimate } } // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging interface PassThroughWalletGasEstimates extends BaseGasEstimatesMixin {} applyMixins(PassThroughWalletGasEstimates, [BaseGasEstimatesMixin]) class PassThroughWalletCallData extends PassThroughWalletTransactions { constructor(clientArgs: SplitsClientConfig) { super({ transactionType: TransactionType.CallData, ...clientArgs, }) } async createPassThroughWallet( args: CreatePassThroughWalletConfig, ): Promise<CallData> { const callData = await this._createPassThroughWalletTransaction(args) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async passThroughTokens(args: PassThroughTokensConfig): Promise<CallData> { const callData = await this._passThroughTokensTransaction(args) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async setPassThrough(args: SetPassThroughConfig): Promise<CallData> { const callData = await this._setPassThroughTransaction(args) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async setPaused(args: PassThroughWalletPauseConfig): Promise<CallData> { const callData = await this._setPausedTransaction(args) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } async execCalls(args: PassThroughWalletExecCallsConfig): Promise<CallData> { const callData = await this._execCallsTransaction(args) if (!this._isCallData(callData)) throw new Error('Invalid response') return callData } }