const Account = require("./account"); const Nat = require("./nat"); const Bytes = require("./bytes"); const RLP = require("./rlp"); const keccak256 = require("./hash").keccak256; // EthereumRPC, IncompleteTransaction -> Promise Transaction const addDefaults = (rpc, tx) => { var baseDefaults = [tx.chainId || rpc("net_version", []), tx.gasPrice || rpc("eth_gasPrice", []), tx.nonce || rpc("eth_getTransactionCount", [tx.from, "latest"]), tx.value || "0x0", tx.data || "0x"]; const noAddress = address => !address || address === "" || address === "0x"; return Promise.all(baseDefaults).then(([chainIdNum, gasPrice, nonce, value, data]) => { var chainId = Nat.fromNumber(chainIdNum); var gasEstimator = tx.gas ? Promise.resolve(null) : rpc("eth_estimateGas", [{ from: noAddress(tx.from) ? null : tx.from, to: noAddress(tx.to) ? null : tx.to, value: tx.value, nonce: tx.nonce, data: tx.data }]); return gasEstimator.then(gasEstimate => { if (gasEstimate.error) { throw gasEstimate.error; } return { chainId: chainId, from: noAddress(tx.from) ? "0x" : tx.from.toLowerCase(), to: noAddress(tx.to) ? "0x" : tx.to.toLowerCase(), gasPrice: gasPrice, gas: tx.gas ? tx.gas : Nat.div(Nat.mul(gasEstimate, "0x6"), "0x5"), nonce: nonce, value: value, data: data ? data.toLowerCase() : null }; }); }); }; // Transaction -> Bytes const signingData = tx => { return RLP.encode([Bytes.fromNat(tx.nonce), Bytes.fromNat(tx.gasPrice), Bytes.fromNat(tx.gas), tx.to ? tx.to.toLowerCase() : "0x", Bytes.fromNat(tx.value), tx.data, Bytes.fromNat(tx.chainId || "0x1"), "0x", "0x"]); }; // Transaction, Account -> Bytes const sign = (tx, account) => { const data = signingData(tx); const signature = Account.makeSigner(Nat.toNumber(tx.chainId || "0x1") * 2 + 35)(keccak256(data), account.privateKey); const rawTransaction = RLP.decode(data).slice(0, 6).concat(Account.decodeSignature(signature)); return RLP.encode(rawTransaction); }; // Bytes -> Address const recover = rawTransaction => { const values = RLP.decode(rawTransaction); const signature = Account.encodeSignature(values.slice(6, 9)); const recovery = Bytes.toNumber(values[6]); const extraData = recovery < 35 ? [] : [Bytes.fromNumber(recovery - 35 >> 1), "0x", "0x"]; const data = values.slice(0, 6).concat(extraData); const dataHex = RLP.encode(data); return Account.recover(keccak256(dataHex), signature); }; module.exports = { addDefaults, signingData, sign, recover };