UNPKG

@river-build/web3

Version:

Dapps for our Space and Registry contracts

185 lines 8.29 kB
import { ethers } from 'ethers'; import { dlogger } from '@river-build/dlog'; export const UNKNOWN_ERROR = 'UNKNOWN_ERROR'; const logger = dlogger('csb:BaseContractShim'); // V2 smart contract shim // todo: replace BaseContractShim with this when refactoring is done export class BaseContractShim { address; contractInterface; provider; signer; abi; readContract; writeContract; constructor(address, provider, abi) { this.address = address; this.provider = provider; this.abi = abi; this.contractInterface = new ethers.utils.Interface(this.abi); } get interface() { return this.contractInterface; } get read() { // lazy create an instance if it is not already cached if (!this.readContract) { this.readContract = this.createReadContractInstance(); } return this.readContract; } write(signer) { // lazy create an instance if it is not already cached if (!this.writeContract) { this.writeContract = this.createWriteContractInstance(signer); } else { // update the signer if it has changed if (this.writeContract.signer !== signer) { this.writeContract = this.createWriteContractInstance(signer); } } return this.writeContract; } decodeFunctionResult(functionName, data) { if (typeof functionName !== 'string') { throw new Error('functionName must be a string'); } if (!this.interface.getFunction(functionName)) { throw new Error(`Function ${functionName} not found in contract interface`); } return this.interface.decodeFunctionResult(functionName, data); } decodeFunctionData(functionName, data) { if (typeof functionName !== 'string') { throw new Error('functionName must be a string'); } if (!this.interface.getFunction(functionName)) { throw new Error(`Function ${functionName} not found in contract interface`); } return this.interface.decodeFunctionData(functionName, data); } encodeFunctionData(functionName, args) { if (typeof functionName !== 'string') { throw new Error('functionName must be a string'); } if (!this.interface.getFunction(functionName)) { throw new Error(`Function ${functionName} not found in contract interface`); } return this.interface.encodeFunctionData(functionName, args); } parseError(error) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any const anyError = error; const { errorData, errorMessage, errorName } = this.getErrorData(anyError); /** * Return early if we have trouble extracting the error data. * Don't know how to decode it. */ if (!errorData) { logger.log(`parseError ${errorName}: no error data, or don't know how to extract error data`); return { name: errorName ?? UNKNOWN_ERROR, message: errorMessage ?? anyError, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access code: anyError?.code, }; } /** * Try to decode the error data. If it fails, return the original error message. */ try { const errDescription = this.interface.parseError(errorData); const decodedError = { name: errDescription?.errorFragment.name ?? UNKNOWN_ERROR, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment message: errorMessage, }; logger.log('decodedError', decodedError); return decodedError; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { // Cannot decode error logger.error('cannot decode error', e); return { name: UNKNOWN_ERROR, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access message: e.message, }; } } // eslint-disable-next-line @typescript-eslint/no-explicit-any getErrorData(anyError) { /** * Error data is nested in different places depending on whether the app is * running in jest/node, or which blockchain (goerli, or anvil). */ // Case: jest/node error // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access let errorData = anyError.error?.error?.error?.data; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access let errorMessage = anyError.error?.error?.error?.message; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access let errorName = anyError.error?.error?.error?.name; if (!errorData) { // Case: Browser (anvil || base goerli) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorData = anyError.error?.error?.data; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorMessage = anyError.error?.error?.message; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorName = anyError.error?.error?.name; } if (!errorData) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorData = anyError.data; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorData = anyError?.data; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorMessage = anyError?.message; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access errorName = anyError?.name; } if (!errorData) { // sometimes it's a stringified object under anyError.reason or anyError.message try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const reason = anyError?.reason || anyError?.message; if (typeof reason === 'string') { const errorMatch = reason?.match(/error\\":\{([^}]+)\}/)?.[1]; if (errorMatch) { const parsedData = JSON.parse(`{${errorMatch?.replace(/\\/g, '')}}`); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access errorData = parsedData?.data; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access errorMessage = parsedData?.message; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access errorName = parsedData?.name; } } } catch (error) { logger.error('error parsing reason', error); } } return { errorData, errorMessage, errorName, }; } parseLog(log) { return this.contractInterface.parseLog(log); } createReadContractInstance() { if (!this.provider) { throw new Error('No provider'); } return new ethers.Contract(this.address, this.abi, this.provider); } createWriteContractInstance(signer) { return new ethers.Contract(this.address, this.abi, signer); } } //# sourceMappingURL=BaseContractShim.js.map