UNPKG

@kaiachain/web3js-ext

Version:
265 lines (261 loc) 14.3 kB
"use strict"; /* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see <http://www.gnu.org/licenses/>. */ // Taken from https://github.com/web3/web3.js/blob/v4.3.0/packages/web3-eth/src/rpc_method_wrappers.ts // Modified to support Klaytn TxTypes Object.defineProperty(exports, "__esModule", { value: true }); exports.sendTransaction = sendTransaction; exports.sendSignedTransaction = sendSignedTransaction; const js_ext_core_1 = require("@kaiachain/js-ext-core"); const web3_core_1 = require("web3-core"); const web3_eth_1 = require("web3-eth"); const web3_types_1 = require("web3-types"); const web3_utils_1 = require("web3-utils"); const web3_validator_1 = require("web3-validator"); const sign_js_1 = require("../accounts/sign.js"); const send_tx_helper_js_1 = require("./utils/send_tx_helper.js"); const transaction_builder_js_1 = require("./utils/transaction_builder.js"); const try_send_transaction_js_1 = require("./utils/try_send_transaction.js"); const wait_for_transaction_receipt_js_1 = require("./utils/wait_for_transaction_receipt.js"); // sendTransaction sends a transaction object. // // It eventually calls one of the following RPC methods: // - eth_sendTransaction // - eth_sendRawTransaction // - klay_sendTransaction // - klay_sendRawTransaction // // High-level logic is as follows: // - Populate some fields // - from, to: resolve wallet index to address // - gasPrice, maxFeePerGas, maxPriorityFeePerGas: do not fill them // - gasLimit: // - Copy tx.gas -> tx.gasLimit. // - Call estimateGas // - If Klaytn TxType, add some buffer // - Convert transaction to be suitable for eth_call RPC // - Use getRpcTxObject() // - Try to find the wallet for the 'from' address. // - Call signAndSend() // - If the wallet is found, sign the transaction to get rawTransaction RLP. // - If Ethereum TxType, call eth_sendRawTransaction // - If Klaytn TxType, call klay_sendRawTransaction // - If the wallet is not found // - If Ethereum TxType, call eth_sendTransaction // - If Klaytn TxType // - If provider is Kaikas, translate the 'type' field to upper snake case string. // - Then call klay_sendTransaction // function sendTransaction(web3Context, transaction, returnFormat, options = { checkRevertBeforeSending: true }) { // Below PromiEvent is a modified version of web3-eth sendTransaction const promiEvent = new web3_core_1.Web3PromiEvent((resolve, reject) => { setImmediate(() => { (async () => { const sendTxHelper = new send_tx_helper_js_1.SendTxHelper({ web3Context, promiEvent, options, returnFormat, }); // Klaytn: replaced formatTransaction() call with getRpcTxObject() to allow Klaytn TxTypes // Resolve 'from' and 'to' field, like in the original web3-eth source const tx = { ...transaction, from: (0, transaction_builder_js_1.getTransactionFromOrToAttr)("from", web3Context, transaction), to: (0, transaction_builder_js_1.getTransactionFromOrToAttr)("to", web3Context, transaction), }; // Klaytn: fill 'gasLimit' field. The Original web3-eth did not fill 'gasLimit', // but Kaikas (window.klaytn) requires 'gas' field nonempty. // Fill 'tx.gasLimit' here, then rename to 'gas' in getRpcTxObject() below. if ((0, web3_validator_1.isNullish)(tx.gasLimit) && !options.ignoreFillingGasLimit) { if (!(0, web3_validator_1.isNullish)(tx.gas)) { tx.gasLimit = tx.gas; } else { const gasLimitHex = await (0, web3_eth_1.estimateGas)(web3Context, tx, "latest", web3_types_1.ETH_DATA_FORMAT); const gasLimitNum = Number(gasLimitHex); const bufferedNum = (0, sign_js_1.bufferedGasLimit)(gasLimitNum); tx.gasLimit = (0, web3_utils_1.format)({ format: "uint" }, bufferedNum, web3_types_1.ETH_DATA_FORMAT); } } // Note: getRpcTxObject() renames 'gasLimit' to 'gas' // transactionFormattedForCall: contains only Ethereum fields. Used to find revert reasons using eth_call. // transactionFormattedForSend: contains all fields including Klaytn-specific fields. This is the transaction to be sent. const transactionFormattedForCall = { ...(0, js_ext_core_1.getRpcTxObject)(tx), // first get formatted fields (only Ethereum fields) type: undefined, // then delete the 'type' field, so that it's interpreted as an Ethereum Tx. }; const transactionFormattedForSend = { ...tx, // first copy all fields from tx (including Klaytn-specific fields) ...(0, js_ext_core_1.getRpcTxObject)(tx), // then overwrite with formatted fields (only Ethereum fields) }; try { // Klaytn: removed sendTxHelper.populateGasPrice() call at here. // - Removed for simplicity, because it's not necessary. // - If wallet is found, then tx is signed locally and be sent via sendRawTransaction. // In this case gas price will be filled by prepareTransaction() // - If wallet isn't found, then tx is sent via sendTransaction, // where gas price will be determined by the RPC endpoint or Browser wallet. await sendTxHelper.checkRevertBeforeSending(transactionFormattedForCall); sendTxHelper.emitSending(transactionFormattedForSend); let wallet; if (web3Context.wallet && !(0, web3_validator_1.isNullish)(tx.from)) { wallet = web3Context.wallet.get(tx.from); } // Klaytn: replaced sendTxHelper.signAndSend() call with signOrSend() to handle Klaytn TxTypes const transactionHash = await signAndSend(web3Context, transactionFormattedForSend, wallet); const transactionHashFormatted = (0, web3_utils_1.format)({ format: "bytes32" }, transactionHash, returnFormat); sendTxHelper.emitSent(transactionFormattedForSend); sendTxHelper.emitTransactionHash(transactionHashFormatted); const transactionReceipt = await (0, wait_for_transaction_receipt_js_1.waitForTransactionReceipt)(web3Context, transactionHash, returnFormat); const transactionReceiptFormatted = sendTxHelper.getReceiptWithEvents((0, web3_utils_1.format)(web3_eth_1.transactionReceiptSchema, transactionReceipt, returnFormat)); sendTxHelper.emitReceipt(transactionReceiptFormatted); resolve(await sendTxHelper.handleResolve({ receipt: transactionReceiptFormatted, tx: transactionFormattedForCall, })); sendTxHelper.emitConfirmation({ receipt: transactionReceiptFormatted, transactionHash, }); } catch (error) { reject(await sendTxHelper.handleError({ error, tx: transactionFormattedForCall, })); } })(); }); }); return promiEvent; } // sendSignedTransaction sends a signed raw transaction. // // It eventually calls one of the following RPC methods: // - eth_sendRawTransaction // - klay_sendRawTransaction function sendSignedTransaction(web3Context, signedTransaction, returnFormat, options = { checkRevertBeforeSending: true }) { // If not Klaytn TxType, fall back to web3-eth's original implementation const txRLP = normalizeSignedTransaction(signedTransaction); const txType = (0, web3_utils_1.hexToNumber)(txRLP.substring(0, 4)); if (!(0, js_ext_core_1.isKlaytnTxType)(txType)) { return (0, web3_eth_1.sendSignedTransaction)(web3Context, signedTransaction, returnFormat, options); } // Below PromiEvent is a modified version of web3-eth sendSignedTransaction const promiEvent = new web3_core_1.Web3PromiEvent((resolve, reject) => { setImmediate(() => { (async () => { const sendTxHelper = new send_tx_helper_js_1.SendTxHelper({ web3Context, promiEvent, options, returnFormat, }); // Klaytn: using js-ext-core:KlaytnTxFactory (inside parseTransaction) instead of // web3-eth-accounts:TransactionFactory const signedTransactionFormattedHex = normalizeSignedTransaction(signedTransaction); const txCallObject = (0, js_ext_core_1.getRpcTxObject)((0, js_ext_core_1.parseTransaction)(signedTransactionFormattedHex)); // argument for eth_call try { // Klaytn: removed v,r,s field removal await sendTxHelper.checkRevertBeforeSending(txCallObject); sendTxHelper.emitSending(signedTransactionFormattedHex); const transactionHash = await (0, try_send_transaction_js_1.trySendTransaction)(web3Context, // Klaytn: Using klay_sendRawTransaction instead of eth_sendRawTransaction async () => web3Context.requestManager.send({ method: "klay_sendRawTransaction", params: [signedTransactionFormattedHex], })); sendTxHelper.emitSent(signedTransactionFormattedHex); const transactionHashFormatted = (0, web3_utils_1.format)({ format: "bytes32" }, transactionHash, returnFormat); sendTxHelper.emitTransactionHash(transactionHashFormatted); const transactionReceipt = await (0, wait_for_transaction_receipt_js_1.waitForTransactionReceipt)(web3Context, transactionHash, returnFormat); const transactionReceiptFormatted = sendTxHelper.getReceiptWithEvents((0, web3_utils_1.format)(web3_eth_1.transactionReceiptSchema, transactionReceipt, returnFormat)); sendTxHelper.emitReceipt(transactionReceiptFormatted); resolve(await sendTxHelper.handleResolve({ receipt: transactionReceiptFormatted, tx: txCallObject, })); sendTxHelper.emitConfirmation({ receipt: transactionReceiptFormatted, transactionHash, }); } catch (error) { reject(await sendTxHelper.handleError({ error, tx: txCallObject, })); } })(); }); }); return promiEvent; } // Convert Bytes(string | Uint8Array) to hex string function normalizeSignedTransaction(signedTransaction) { if (signedTransaction instanceof Uint8Array) { signedTransaction = (0, web3_utils_1.bytesToHex)(signedTransaction); } if (signedTransaction.length < 4 || !signedTransaction.startsWith("0x")) { throw new Error(`Invalid signed transaction '${signedTransaction}'`); } return signedTransaction; } // Call one of the following RPC methods: // - eth_sendTransaction // - eth_sendRawTransaction // - klay_sendTransaction // - klay_sendRawTransaction // // Modified from web3-eth/src/send_tx/send_tx_helper.ts:signAndSend() async function signAndSend(web3Context, tx, senderAccount) { if (senderAccount) { // senderAccount (i.e. private key) is given. Sign and sendRawTransaction. const signResult = await senderAccount.signTransaction(tx); let method; if ((0, js_ext_core_1.isKlaytnTxType)((0, sign_js_1._parseTxType)(tx.type))) { method = "klay_sendRawTransaction"; } else { method = "eth_sendRawTransaction"; } return await (0, try_send_transaction_js_1.trySendTransaction)(web3Context, async () => web3Context.requestManager.send({ method: method, params: [signResult.rawTransaction], }), signResult.transactionHash); } else { // senderAccount (i.e. private key) is not given. Call sendTransaction // and let provider (remote RPC endpoint or Browser wallet) do the rest. // Translate to string 'type' field that Kaikas understands. if ((0, js_ext_core_1.isKlaytnTxType)((0, sign_js_1._parseTxType)(tx.type)) && isKaikas(web3Context.provider)) { tx.type = (0, js_ext_core_1.getKaikasTxType)((0, sign_js_1._parseTxType)(tx.type)); } let method; if ((0, js_ext_core_1.isKlaytnTxType)((0, sign_js_1._parseTxType)(tx.type))) { method = "klay_sendTransaction"; } else { method = "eth_sendTransaction"; } return await (0, try_send_transaction_js_1.trySendTransaction)(web3Context, async () => web3Context.requestManager.send({ method: method, params: [tx], })); } } function isKaikas(provider) { return !(0, web3_validator_1.isNullish)(provider) && (provider.isKaikas == true); }