Skip to content

Commit

Permalink
Ethers v6 (#13)
Browse files Browse the repository at this point in the history
Ethers v6 support
  • Loading branch information
nicolasbrugneaux authored Oct 18, 2023
1 parent 65a5395 commit 882551e
Show file tree
Hide file tree
Showing 17 changed files with 1,136 additions and 1,620 deletions.
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@celo-tools/celo-ethers-wrapper",
"version": "1.0.0",
"version": "2.0.0",
"description": "A minimal wrapper to make Ethers.JS compatible with the Celo network.",
"main": "build/main/index.js",
"typings": "build/main/index.d.ts",
Expand All @@ -26,13 +26,13 @@
"node": ">=10"
},
"devDependencies": {
"@types/node": "^16.11.10",
"ethers": "^5.7.2",
"ts-node": "^10.8.1",
"typescript": "^4.5.2"
"@types/node": "^20.8.2",
"ethers": "^6.7.1",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
},
"peerDependencies": {
"ethers": "^5"
"ethers": "^6"
},
"files": [
"build/main",
Expand Down
2 changes: 1 addition & 1 deletion src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ export const EIP155_NUMBER = 35;
export const EIGHT = 8;

// NOTE: Logic stolen from https://github.com/celo-org/celo-monorepo/blob/e7ebc92cb0715dc56c9d7f613dca81e076541cf3/packages/sdk/connect/src/connection.ts#L382-L396
export const GAS_INFLATION_FACTOR = 1.3;
export const GAS_INFLATION_FACTOR = 130n;
174 changes: 81 additions & 93 deletions src/lib/CeloProvider.ts
Original file line number Diff line number Diff line change
@@ -1,128 +1,116 @@
import { BigNumber, providers, utils } from "ethers";
import { getNetwork } from "./networks";
import {
JsonRpcProvider,
PerformActionRequest,
TransactionResponse,
TransactionResponseParams,
getBigInt,
resolveProperties,
toBeHex,
} from "ethers";
import { CeloTransactionRequest, parseCeloTransaction } from "./transactions";

const logger = new utils.Logger("CeloProvider");
export default class CeloProvider extends JsonRpcProvider {
async _perform(req: PerformActionRequest): Promise<any> {
// Legacy networks do not like the type field being passed along (which
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
if (req.method === "call" || req.method === "estimateGas") {
let tx = req.transaction;
if (tx && tx.type != null && getBigInt(tx.type)) {
// If there are no EIP-1559 properties, it might be non-EIP-1559
if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
const feeData = await this.getFeeData();
if (
feeData.maxFeePerGas == null &&
feeData.maxPriorityFeePerGas == null
) {
// Network doesn't know about EIP-1559 (and hence type)
req = Object.assign({}, req, {
transaction: Object.assign({}, tx, { type: undefined }),
});
}
}
}
}

export class CeloProvider extends providers.JsonRpcProvider {
/**
* Override to parse transaction correctly
* https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/base-provider.ts
*/
async sendTransaction(
signedTransaction: string | Promise<string>
): Promise<providers.TransactionResponse> {
await this.getNetwork();
const hexTx = await Promise.resolve(signedTransaction).then((t) =>
utils.hexlify(t)
);
const tx = parseCeloTransaction(hexTx);
const blockNumber = await this._getInternalBlockNumber(
100 + 2 * this.pollingInterval
);
try {
const hash = await this.perform("sendTransaction", {
signedTransaction: hexTx,
});
return this._wrapTransaction(tx, hash, blockNumber);
} catch (error) {
(<any>error).transaction = tx;
(<any>error).transactionHash = tx.hash;
throw error;
const request = this.getRpcRequest(req);

if (request != null) {
return await this.send(request.method, request.args);
}
}

/**
* Override to handle alternative gas currencies
* getGasPrice in https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/base-provider.ts
*/
async getGasPrice(feeCurrencyAddress?: string) {
await this.getNetwork();
const params = feeCurrencyAddress ? { feeCurrencyAddress } : {};
return BigNumber.from(await this.perform("getGasPrice", params));
return super._perform(req);
}

/**
* Override to handle alternative gas currencies
* prepareRequest in https://github.com/ethers-io/ethers.js/blob/master/packages/providers/src.ts/json-rpc-provider.ts
*/
prepareRequest(method: any, params: any): [string, Array<any>] {
if (method === "getGasPrice") {
const param = params.feeCurrencyAddress
? [params.feeCurrencyAddress]
getRpcRequest(
req: PerformActionRequest
): null | { method: string; args: Array<any> } {
if (req.method === "getGasPrice") {
// @ts-expect-error
const param = req.feeCurrencyAddress
? // @ts-expect-error
[req.feeCurrencyAddress]
: [];
return ["eth_gasPrice", param];
return { method: "eth_gasPrice", args: param };
}

if (method === "estimateGas") {
// NOTE: somehow estimategas trims lots of fields
// this overrides it
if (req.method === "estimateGas") {
const extraneous_keys = [
["from", (x: string) => x],
["feeCurrency", utils.hexlify],
["feeCurrency", (x: string) => x],
["gatewayFeeRecipient", (x: string) => x],
["gatewayFee", utils.hexlify],
["gatewayFee", toBeHex],
] as const;

const tx = {
...providers.JsonRpcProvider.hexlifyTransaction(
params.transaction,
extraneous_keys.reduce((acc, [key]) => {
acc[key] = true;
return acc;
}, {} as Record<string, true>)
),
...this.getRpcTransaction(req.transaction),
};
extraneous_keys.forEach(([key, fn]) => {
if (params.transaction[key]) {
tx[key] = fn(params.transaction[key]);
// @ts-expect-error
if (req.transaction[key]) {
// @ts-expect-error
tx[key] = fn(req.transaction[key]);
}
});

return ["eth_estimateGas", [tx]];
return { method: "eth_estimateGas", args: [tx] };
}

return super.prepareRequest(method, params);
return super.getRpcRequest(req);
}

static getNetwork(networkish: providers.Networkish): providers.Network {
const network = getNetwork(networkish == null ? "celo" : networkish);
if (network == null) {
return logger.throwError(
`unknown network: ${JSON.stringify(network)}`,
utils.Logger.errors.UNSUPPORTED_OPERATION,
{
operation: "getNetwork",
value: networkish,
}
);
}
return network;
async estimateGas(_tx: CeloTransactionRequest): Promise<bigint> {
return getBigInt(
await this._perform({
method: "estimateGas",
// @ts-ignore
transaction: _tx,
}),
"%response"
);
}

async estimateGas(
transaction: utils.Deferrable<CeloTransactionRequest>
): Promise<BigNumber> {
// NOTE: Overrides the ethers method to make sure feeCurrency and from are sent
// to the rpc node
await this.getNetwork();
const params = await utils.resolveProperties({
transaction,
// Overrides
// https://github.com/ethers-io/ethers.js/blob/main/lib.esm/providers/abstract-provider.js#L716-L730
// Just changes the tx parsing.
async broadcastTransaction(signedTx: string): Promise<TransactionResponse> {
const { hash } = await resolveProperties({
blockNumber: this.getBlockNumber(),
hash: this._perform({
method: "broadcastTransaction",
signedTransaction: signedTx,
}),
network: this.getNetwork(),
});
const result = await this.perform("estimateGas", params);
try {
return BigNumber.from(result);
} catch (error) {
return logger.throwError(
"bad result from backend",
utils.Logger.errors.SERVER_ERROR,
{
method: "estimateGas",
params,
result,
error,
}
);

const tx = parseCeloTransaction(signedTx);
if (tx.hash !== hash) {
throw new Error("@TODO: the returned hash did not match");
}

return new TransactionResponse(tx as TransactionResponseParams, this);
}
}
Loading

0 comments on commit 882551e

Please sign in to comment.