UNPKG

@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
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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