@river-build/web3
Version:
Dapps for our Space and Registry contracts
185 lines • 8.29 kB
JavaScript
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