-
Notifications
You must be signed in to change notification settings - Fork 8
client impl part 1 #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
client impl part 1 #112
Conversation
|
Claude finished @randygrok's task —— View job Code Review Completed ✅I've thoroughly reviewed this client implementation for EvNode transactions. Here's my comprehensive feedback:
SummaryThis is a well-structured TypeScript client library for EvNode transactions with good Viem integration. The implementation is comprehensive but has several important issues that should be addressed, particularly around gas estimation, error handling, and potential security concerns. 🔴 Critical Issues1. Gas Estimation Incomplete -
|
Summary of ChangesHello @randygrok, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request establishes the foundational client-side logic for a new EvNode transaction system. It defines the structure and behavior of these specialized transactions, enabling developers to programmatically construct, sign with multiple parties (executor and sponsor), and dispatch them to the network. The implementation focuses on robust data handling, signature management, and integration with existing blockchain interaction libraries to streamline the development of applications utilizing this new transaction type. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a Viem client for a custom EvNode transaction type. The implementation is comprehensive, covering transaction encoding/decoding, signing, and client-side actions. My review focuses on improving robustness, correctness of gas estimations, and maintainability. I've identified a few areas for improvement, including more accurate gas calculations, safer error handling in RPC calls, and addressing potential issues with the eth_sign method. I've also suggested some minor refactoring to reduce code duplication.
| export function hashSignerFromRpcClient( | ||
| client: Client, | ||
| address: Address, | ||
| ): HashSigner { | ||
| return { | ||
| address, | ||
| signHash: async (hash) => { | ||
| // eth_sign is expected to sign raw bytes (no EIP-191 prefix). | ||
| const signature = await client.request({ | ||
| method: 'eth_sign', | ||
| params: [address, hash], | ||
| }); | ||
| if (!isHex(signature)) { | ||
| throw new Error('eth_sign returned non-hex signature'); | ||
| } | ||
| return signature; | ||
| }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The eth_sign RPC method has inconsistent behavior across different Ethereum node implementations and is generally considered deprecated. Some nodes (like Geth) will sign the raw hash as expected here, but others (like Parity/OpenEthereum) will prefix the hash according to EIP-191, which would lead to invalid signatures for this use case. This can cause hard-to-debug issues for users of your client depending on which RPC provider they use.
It's highly recommended to add a prominent warning in the function's documentation about this limitation and the specific node behavior it relies on.
| async function fetchMaxPriorityFee(client: Client): Promise<bigint> { | ||
| try { | ||
| const result = await client.request({ method: 'eth_maxPriorityFeePerGas' }); | ||
| if (!isHex(result)) throw new Error('eth_maxPriorityFeePerGas returned non-hex'); | ||
| return hexToBigIntSafe(result); | ||
| } catch { | ||
| return 0n; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The broad catch block here will swallow any error from the eth_maxPriorityFeePerGas call and default to 0n. This can hide underlying problems like network connectivity issues or RPC misconfigurations, leading to transactions being sent with a potentially undesirable maxPriorityFeePerGas. It's safer to only catch specific errors that indicate the method is not supported (like on a pre-EIP-1559 chain) and re-throw others.
try {
const result = await client.request({ method: 'eth_maxPriorityFeePerGas' });
if (!isHex(result)) throw new Error('eth_maxPriorityFeePerGas returned non-hex');
return hexToBigIntSafe(result);
} catch (err) {
// Only fallback to 0n if the method is not supported.
// A robust implementation would check for a specific RPC error code (e.g., -32601).
if (err instanceof Error && (err.message.includes('not found') || err.message.includes('not supported'))) {
return 0n;
}
throw err;
}| export function estimateIntrinsicGas(calls: Call[]): bigint { | ||
| let gas = 21000n; | ||
|
|
||
| for (const call of calls) { | ||
| if (call.to === null) gas += 32000n; | ||
|
|
||
| for (const byte of hexToBytes(call.data)) { | ||
| gas += byte === 0 ? 4n : 16n; | ||
| } | ||
| } | ||
|
|
||
| return gas; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation of estimateIntrinsicGas does not account for the gas costs associated with an accessList (per EIP-2930). This will lead to an underestimation of the intrinsic gas for transactions using access lists, potentially causing them to fail. The function should be updated to accept an accessList and include its cost in the calculation.
export function estimateIntrinsicGas(calls: Call[], accessList?: AccessList): bigint {
let gas = 21000n;
for (const call of calls) {
if (call.to === null) gas += 32000n;
for (const byte of hexToBytes(call.data)) {
gas += byte === 0 ? 4n : 16n;
}
}
if (accessList) {
const ACCESS_LIST_ADDRESS_COST = 2400n;
const ACCESS_LIST_STORAGE_KEY_COST = 1900n;
gas += BigInt(accessList.length) * ACCESS_LIST_ADDRESS_COST;
for (const item of accessList) {
gas += BigInt(item.storageKeys.length) * ACCESS_LIST_STORAGE_KEY_COST;
}
}
return gas;
}
clients/src/index.ts
Outdated
| async sendEvNodeTransaction(args: { | ||
| calls: Call[]; | ||
| executor: HashSigner; | ||
| chainId?: bigint; | ||
| nonce?: bigint; | ||
| maxFeePerGas?: bigint; | ||
| maxPriorityFeePerGas?: bigint; | ||
| gasLimit?: bigint; | ||
| accessList?: AccessList; | ||
| }): Promise<Hex> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The args parameter uses an inline anonymous type that is very similar to the EvnodeSendArgs interface defined earlier. This creates code duplication and can make maintenance harder.
To improve this, consider defining a specific type for these arguments that extends EvnodeSendArgs but makes executor a required property. This would make the code more DRY and easier to reason about.
clients/src/index.ts
Outdated
| async createSponsorableIntent(args: { | ||
| calls: Call[]; | ||
| executor: HashSigner; | ||
| chainId?: bigint; | ||
| nonce?: bigint; | ||
| maxFeePerGas?: bigint; | ||
| maxPriorityFeePerGas?: bigint; | ||
| gasLimit?: bigint; | ||
| accessList?: AccessList; | ||
| }): Promise<SponsorableIntent> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const nonce = overrides.nonce ?? (await fetchNonce(client, address)); | ||
| const maxPriorityFeePerGas = | ||
| overrides.maxPriorityFeePerGas ?? (await fetchMaxPriorityFee(client)); | ||
| const maxFeePerGas = overrides.maxFeePerGas ?? (await fetchGasPrice(client)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defaulting maxFeePerGas to the result of eth_gasPrice can be suboptimal for EIP-1559 transactions, as it might lead to overpaying for gas. A more conventional approach is to calculate maxFeePerGas as baseFeePerGas + maxPriorityFeePerGas. This would require fetching the baseFeePerGas from the latest block.
| const maxPriorityFeePerGas = | ||
| overrides.maxPriorityFeePerGas ?? (await fetchMaxPriorityFee(client)); | ||
| const maxFeePerGas = overrides.maxFeePerGas ?? (await fetchGasPrice(client)); | ||
| const gasLimit = overrides.gasLimit ?? estimateIntrinsicGas(calls); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Add 21000 gas per call in estimateIntrinsicGas for batch support - Unify env vars to use EXECUTOR_PRIVATE_KEY across all tests - Rewrite evnode-flows.ts with balance verification for all 4 flows
Description
Closes: #109