@celo-tools/celo-ethers-wrapper
Version:
A minimal wrapper to make Ethers.JS compatible with the Celo network.
162 lines (161 loc) • 13.4 kB
JavaScript
import { assertArgument, getAddress, HDNodeWallet, keccak256, Mnemonic, resolveProperties, Wallet, } from "ethers";
import CeloProvider from "./CeloProvider";
import { adjustForGasInflation, convertFromCeloToToken, isEmpty, } from "./transaction/utils";
import { serializeCeloTransaction, } from "./transactions";
import { L2_PROXY_ADMIN_ADDRESS } from "../consts";
import { getConversionRateFromCeloToToken } from "./CoreContract";
const forwardErrors = [
"INSUFFICIENT_FUNDS",
"NONCE_EXPIRED",
"REPLACEMENT_UNDERPRICED",
];
export default class CeloWallet extends Wallet {
async isCel2() {
const code = await this.provider?.getCode(L2_PROXY_ADMIN_ADDRESS);
if (typeof code === "string") {
return code != "0x" && code.length > 2;
}
return false;
}
/**
* Override to skip checkTransaction step which rejects Celo tx properties
* https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-signer/src.ts/index.ts
*/
async populateTransaction(transaction) {
let tx = await resolveProperties(transaction);
if (isEmpty(tx.from)) {
tx.from = this.address;
}
if (isEmpty(tx.nonce)) {
tx.nonce = await this.provider?.getTransactionCount(tx.from, "pending");
}
if (isEmpty(tx.gasLimit)) {
try {
tx.gasLimit = await this.estimateGas(tx);
}
catch (error) {
// If there is an error code it's an expected error
if (forwardErrors.indexOf(error.code) >= 0) {
throw error;
}
// If there is no error code it's an unexpected error
assertArgument(false, "cannot estimate gas; transaction may fail or may require manual gas limit", "transaction", {
error: error,
tx: tx,
});
}
}
await this.populateFees(tx);
if (isEmpty(tx.chainId)) {
tx.chainId = (await this.provider.getNetwork()).chainId;
}
else {
tx.chainId = Promise.all([
tx.chainId,
(await this.provider.getNetwork()).chainId,
]).then(([txChainId, providerChainId]) => {
if (providerChainId !== 0n && txChainId !== providerChainId) {
assertArgument(false, "chainId address mismatch", "transaction", transaction);
}
return txChainId;
});
}
return resolveProperties(tx);
}
// sets feedata for the transaction.
//
async populateFees(tx) {
const noFeeCurrency = !tx.feeCurrency;
const useCIP66ForEasyFeeTransactions = false; // cip66 is not yet implemented in nodes
// CIP 66 transactions are denominated in CELO not the fee token
const feesAreInCELO = noFeeCurrency || useCIP66ForEasyFeeTransactions;
if (isEmpty(tx.maxPriorityFeePerGas) || isEmpty(tx.maxFeePerGas)) {
const { maxFeePerGas, maxPriorityFeePerGas } = (await this.provider?.getFeeData(tx.feeCurrency, feesAreInCELO));
tx.maxFeePerGas = maxFeePerGas;
tx.maxPriorityFeePerGas = maxPriorityFeePerGas;
if (useCIP66ForEasyFeeTransactions && isEmpty(tx.maxFeeInFeeCurrency)) {
const gasLimit = BigInt(tx.gasLimit);
const maxFeeInFeeCurrency = await this.estimateMaxFeeInFeeToken({
feeCurrency: tx.feeCurrency,
gasLimit,
maxFeePerGas: maxFeePerGas,
});
tx.maxFeeInFeeCurrency = maxFeeInFeeCurrency;
}
}
return tx;
}
/**
* Override to serialize transaction using custom serialize method
* https://github.com/ethers-io/ethers.js/blob/master/packages/wallet/src.ts/index.ts
*/
async signTransaction(transaction) {
const tx = await this.populateTransaction(transaction);
if (tx.from != null) {
if (getAddress(tx.from) !== this.address) {
assertArgument(false, "transaction from address mismatch", "transaction.from", transaction.from);
}
delete tx.from;
}
const signature = this.signingKey.sign(keccak256(serializeCeloTransaction(tx)));
const serialized = serializeCeloTransaction(tx, signature);
return serialized;
}
/**
* Override to serialize transaction using custom serialize method
*/
async sendTransaction(transaction) {
const provider = this.provider;
const pop = await this.populateTransaction(transaction);
delete pop.from;
return await provider.broadcastTransaction(await this.signTransaction(pop));
}
/**
* Override to skip checkTransaction step which rejects Celo tx properties
* https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-signer/src.ts/index.ts
*/
async estimateGas(transaction) {
return this.provider.estimateGas(transaction).then(adjustForGasInflation);
}
/**
* @remarks For cip 66 transactions it is necessary to provide the absolute limit one is willing to pay denominated in the token.
* In contrast with earlier tx types for fee currencies (celo legacy, cip42, cip 64).
* Calulating Estimation requires the gas, maxfeePerGas and the conversion rate from CELO to feeToken
* https://github.com/celo-org/celo-proposals/blob/master/CIPs/cip-0066.md
*
* @dev cip66 has yet to be implemented on celo nodes. Dont add maxFeeInFeeCurrency to your transaction. use cip64
*/
async estimateMaxFeeInFeeToken({ gasLimit, maxFeePerGas, feeCurrency, }) {
const maxGasFeesInCELO = gasLimit * maxFeePerGas;
const [numerator, denominator] = await getConversionRateFromCeloToToken(feeCurrency, this);
const feeDenominatedInToken = convertFromCeloToToken({
amountInCelo: maxGasFeesInCELO,
ratioTOKEN: numerator,
ratioCELO: denominator,
});
return feeDenominatedInToken;
}
/**
* Override to support alternative gas currencies
* @dev (for cip66 txn you want gasPrice in CELO so dont pass in the feeToken)
* https://github.com/celo-tools/ethers.js/blob/master/packages/abstract-signer/src.ts/index.ts
*/
async getGasPrice(feeCurrencyAddress) {
return await this.provider._perform({
method: "getGasPrice",
feeCurrencyAddress,
});
}
static fromMnemonic(phrase, path, wordlist) {
const hdWallet = HDNodeWallet.fromMnemonic(Mnemonic.fromPhrase(phrase, null, wordlist), path);
return new CeloWallet(hdWallet.privateKey, new CeloProvider());
}
/**
* Override just for type fix
* https://github.com/ethers-io/ethers.js/blob/master/packages/wallet/src.ts/index.ts
*/
connect(provider) {
return new CeloWallet(this.signingKey, provider);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ2Vsb1dhbGxldC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9saWIvQ2Vsb1dhbGxldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsY0FBYyxFQUVkLFVBQVUsRUFDVixZQUFZLEVBQ1osU0FBUyxFQUNULFFBQVEsRUFHUixpQkFBaUIsRUFFakIsTUFBTSxHQUVQLE1BQU0sUUFBUSxDQUFDO0FBQ2hCLE9BQU8sWUFBWSxNQUFNLGdCQUFnQixDQUFDO0FBQzFDLE9BQU8sRUFDTCxxQkFBcUIsRUFDckIsc0JBQXNCLEVBQ3RCLE9BQU8sR0FDUixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sRUFHTCx3QkFBd0IsR0FDekIsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDbkQsT0FBTyxFQUFFLGdDQUFnQyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFbEUsTUFBTSxhQUFhLEdBQUc7SUFDcEIsb0JBQW9CO0lBQ3BCLGVBQWU7SUFDZix5QkFBeUI7Q0FDWCxDQUFDO0FBRWpCLE1BQU0sQ0FBQyxPQUFPLE9BQU8sVUFBVyxTQUFRLE1BQU07SUFDNUMsS0FBSyxDQUFDLE1BQU07UUFDVixNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLHNCQUFzQixDQUFDLENBQUM7UUFDbEUsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLEVBQUU7WUFDNUIsT0FBTyxJQUFJLElBQUksSUFBSSxJQUFJLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO1NBQ3hDO1FBQ0QsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsS0FBSyxDQUFDLG1CQUFtQixDQUN2QixXQUFtQztRQUVuQyxJQUFJLEVBQUUsR0FBUSxNQUFNLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBRW5ELElB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