import {
Chain,
ContractFunctionArgs,
ContractFunctionName,
ReadContractParameters,
ReadContractReturnType,
SimulateContractParameters,
SimulateContractReturnType,
TransactionReceipt,
WriteContractParameters,
} from 'viem';
import { WalletNotConnectedError } from '../errors/sdk.errors';
import { ContractNames, SdkSupportedChainIds, getChain, getMintClubContractAddress } from '../exports';
import { Client } from '../helpers/ClientHelper';
import { SupportedAbiType } from '../types/abi.types';
import { GenericWriteParams, TokenContractReadArgs } from '../types/transactions.types';
import { customWaitForTransaction } from '../utils/transaction';
type GenericLogicConstructorParams<
A extends SupportedAbiType = SupportedAbiType,
C extends ContractNames = ContractNames,
> = {
chainId: SdkSupportedChainIds;
type: C;
abi: A;
};
export class GenericContractLogic<
A extends SupportedAbiType = SupportedAbiType,
C extends ContractNames = ContractNames,
> {
private abi: A;
private contractType: C;
private chainId: SdkSupportedChainIds;
private clientHelper: Client;
private chain: Chain;
constructor(params: GenericLogicConstructorParams) {
const { chainId, type, abi } = params;
this.contractType = type;
this.abi = abi;
this.chainId = chainId;
this.chain = getChain(chainId);
this.clientHelper = new Client();
}
public read<
T extends ContractFunctionName,
R extends ContractFunctionArgs,
>(params: TokenContractReadArgs): Promise> {
const { functionName, blockNumber } = params as TokenContractReadArgs;
const args = 'args' in params ? params.args : undefined;
let address: `0x${string}`;
if ('tokenAddress' in params) {
address = params.tokenAddress;
} else {
address = getMintClubContractAddress(this.contractType, this.chainId);
}
const publicClient = this.clientHelper._getPublicClient(this.chainId);
return publicClient.readContract({
abi: this.abi,
address,
functionName,
args,
...(blockNumber !== undefined ? { blockNumber } : {}),
} as unknown as ReadContractParameters) as Promise>;
}
public async write<
T extends ContractFunctionName,
R extends ContractFunctionArgs,
>(params: GenericWriteParams) {
const { functionName, value, debug, onError, onSignatureRequest: onSignatureRequest, onSigned, onSuccess } = params;
let args, simulationArgs;
args = 'args' in params ? params.args : undefined;
let address: `0x${string}`;
if ('tokenAddress' in params) {
address = params.tokenAddress;
} else {
address = getMintClubContractAddress(this.contractType, this.chainId);
}
try {
const walletClient = this.clientHelper.getWalletClient();
const isPrivateKey = this.clientHelper.isPrivateKey();
if (isPrivateKey && !walletClient?.account) {
throw new WalletNotConnectedError();
} else if (!walletClient || !walletClient.account) {
await this.clientHelper.connect();
return;
} else if (!isPrivateKey && walletClient.chain?.id !== this.chainId) {
await walletClient.addChain?.({ chain: this.chain });
await walletClient.switchChain?.({ id: this.chainId });
}
simulationArgs = {
chain: this.chain,
account: walletClient.account,
abi: this.abi,
address,
functionName,
args,
...(value !== undefined && { value }),
} as unknown as SimulateContractParameters;
debug?.(simulationArgs);
let tx: `0x${string}` | undefined;
// If wallet client is available, use it
if (isPrivateKey) {
const publicClient = this.clientHelper._getPublicClient(this.chainId);
const { request } = (await publicClient.simulateContract(simulationArgs)) as SimulateContractReturnType<
A,
T,
R
>;
onSignatureRequest?.();
tx = await walletClient.writeContract(request as WriteContractParameters);
} else {
const { request } = (await this.clientHelper
._getPublicClient(this.chainId)
.simulateContract(simulationArgs)) as SimulateContractReturnType;
onSignatureRequest?.();
tx = await walletClient.writeContract(request as WriteContractParameters);
}
onSigned?.(tx);
// const receipt = await this.clientHelper._getPublicClient(this.chainId).waitForTransactionReceipt({
// hash: tx,
// });
// use custom wait for transaction for better stability
const receipt = await customWaitForTransaction(this.chainId, tx);
onSuccess?.(receipt as TransactionReceipt);
return receipt;
} catch (e) {
if (e) {
Object.assign(e, { functionName, args, simulationArgs, value });
}
onError?.(e);
return;
}
}
}