transaction.js 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981
  1. import { getAddress } from "../address/index.js";
  2. import { ZeroAddress } from "../constants/addresses.js";
  3. import { keccak256, sha256, Signature, SigningKey } from "../crypto/index.js";
  4. import { concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify, assert, assertArgument, isBytesLike, isHexString, toBeArray, zeroPadValue } from "../utils/index.js";
  5. import { accessListify } from "./accesslist.js";
  6. import { recoverAddress } from "./address.js";
  7. const BN_0 = BigInt(0);
  8. const BN_2 = BigInt(2);
  9. const BN_27 = BigInt(27);
  10. const BN_28 = BigInt(28);
  11. const BN_35 = BigInt(35);
  12. const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
  13. const BLOB_SIZE = 4096 * 32;
  14. function getVersionedHash(version, hash) {
  15. let versioned = version.toString(16);
  16. while (versioned.length < 2) {
  17. versioned = "0" + versioned;
  18. }
  19. versioned += sha256(hash).substring(4);
  20. return "0x" + versioned;
  21. }
  22. function handleAddress(value) {
  23. if (value === "0x") {
  24. return null;
  25. }
  26. return getAddress(value);
  27. }
  28. function handleAccessList(value, param) {
  29. try {
  30. return accessListify(value);
  31. }
  32. catch (error) {
  33. assertArgument(false, error.message, param, value);
  34. }
  35. }
  36. function handleNumber(_value, param) {
  37. if (_value === "0x") {
  38. return 0;
  39. }
  40. return getNumber(_value, param);
  41. }
  42. function handleUint(_value, param) {
  43. if (_value === "0x") {
  44. return BN_0;
  45. }
  46. const value = getBigInt(_value, param);
  47. assertArgument(value <= BN_MAX_UINT, "value exceeds uint size", param, value);
  48. return value;
  49. }
  50. function formatNumber(_value, name) {
  51. const value = getBigInt(_value, "value");
  52. const result = toBeArray(value);
  53. assertArgument(result.length <= 32, `value too large`, `tx.${name}`, value);
  54. return result;
  55. }
  56. function formatAccessList(value) {
  57. return accessListify(value).map((set) => [set.address, set.storageKeys]);
  58. }
  59. function formatHashes(value, param) {
  60. assertArgument(Array.isArray(value), `invalid ${param}`, "value", value);
  61. for (let i = 0; i < value.length; i++) {
  62. assertArgument(isHexString(value[i], 32), "invalid ${ param } hash", `value[${i}]`, value[i]);
  63. }
  64. return value;
  65. }
  66. function _parseLegacy(data) {
  67. const fields = decodeRlp(data);
  68. assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 6), "invalid field count for legacy transaction", "data", data);
  69. const tx = {
  70. type: 0,
  71. nonce: handleNumber(fields[0], "nonce"),
  72. gasPrice: handleUint(fields[1], "gasPrice"),
  73. gasLimit: handleUint(fields[2], "gasLimit"),
  74. to: handleAddress(fields[3]),
  75. value: handleUint(fields[4], "value"),
  76. data: hexlify(fields[5]),
  77. chainId: BN_0
  78. };
  79. // Legacy unsigned transaction
  80. if (fields.length === 6) {
  81. return tx;
  82. }
  83. const v = handleUint(fields[6], "v");
  84. const r = handleUint(fields[7], "r");
  85. const s = handleUint(fields[8], "s");
  86. if (r === BN_0 && s === BN_0) {
  87. // EIP-155 unsigned transaction
  88. tx.chainId = v;
  89. }
  90. else {
  91. // Compute the EIP-155 chain ID (or 0 for legacy)
  92. let chainId = (v - BN_35) / BN_2;
  93. if (chainId < BN_0) {
  94. chainId = BN_0;
  95. }
  96. tx.chainId = chainId;
  97. // Signed Legacy Transaction
  98. assertArgument(chainId !== BN_0 || (v === BN_27 || v === BN_28), "non-canonical legacy v", "v", fields[6]);
  99. tx.signature = Signature.from({
  100. r: zeroPadValue(fields[7], 32),
  101. s: zeroPadValue(fields[8], 32),
  102. v
  103. });
  104. //tx.hash = keccak256(data);
  105. }
  106. return tx;
  107. }
  108. function _serializeLegacy(tx, sig) {
  109. const fields = [
  110. formatNumber(tx.nonce, "nonce"),
  111. formatNumber(tx.gasPrice || 0, "gasPrice"),
  112. formatNumber(tx.gasLimit, "gasLimit"),
  113. (tx.to || "0x"),
  114. formatNumber(tx.value, "value"),
  115. tx.data,
  116. ];
  117. let chainId = BN_0;
  118. if (tx.chainId != BN_0) {
  119. // A chainId was provided; if non-zero we'll use EIP-155
  120. chainId = getBigInt(tx.chainId, "tx.chainId");
  121. // We have a chainId in the tx and an EIP-155 v in the signature,
  122. // make sure they agree with each other
  123. assertArgument(!sig || sig.networkV == null || sig.legacyChainId === chainId, "tx.chainId/sig.v mismatch", "sig", sig);
  124. }
  125. else if (tx.signature) {
  126. // No explicit chainId, but EIP-155 have a derived implicit chainId
  127. const legacy = tx.signature.legacyChainId;
  128. if (legacy != null) {
  129. chainId = legacy;
  130. }
  131. }
  132. // Requesting an unsigned transaction
  133. if (!sig) {
  134. // We have an EIP-155 transaction (chainId was specified and non-zero)
  135. if (chainId !== BN_0) {
  136. fields.push(toBeArray(chainId));
  137. fields.push("0x");
  138. fields.push("0x");
  139. }
  140. return encodeRlp(fields);
  141. }
  142. // @TODO: We should probably check that tx.signature, chainId, and sig
  143. // match but that logic could break existing code, so schedule
  144. // this for the next major bump.
  145. // Compute the EIP-155 v
  146. let v = BigInt(27 + sig.yParity);
  147. if (chainId !== BN_0) {
  148. v = Signature.getChainIdV(chainId, sig.v);
  149. }
  150. else if (BigInt(sig.v) !== v) {
  151. assertArgument(false, "tx.chainId/sig.v mismatch", "sig", sig);
  152. }
  153. // Add the signature
  154. fields.push(toBeArray(v));
  155. fields.push(toBeArray(sig.r));
  156. fields.push(toBeArray(sig.s));
  157. return encodeRlp(fields);
  158. }
  159. function _parseEipSignature(tx, fields) {
  160. let yParity;
  161. try {
  162. yParity = handleNumber(fields[0], "yParity");
  163. if (yParity !== 0 && yParity !== 1) {
  164. throw new Error("bad yParity");
  165. }
  166. }
  167. catch (error) {
  168. assertArgument(false, "invalid yParity", "yParity", fields[0]);
  169. }
  170. const r = zeroPadValue(fields[1], 32);
  171. const s = zeroPadValue(fields[2], 32);
  172. const signature = Signature.from({ r, s, yParity });
  173. tx.signature = signature;
  174. }
  175. function _parseEip1559(data) {
  176. const fields = decodeRlp(getBytes(data).slice(1));
  177. assertArgument(Array.isArray(fields) && (fields.length === 9 || fields.length === 12), "invalid field count for transaction type: 2", "data", hexlify(data));
  178. const tx = {
  179. type: 2,
  180. chainId: handleUint(fields[0], "chainId"),
  181. nonce: handleNumber(fields[1], "nonce"),
  182. maxPriorityFeePerGas: handleUint(fields[2], "maxPriorityFeePerGas"),
  183. maxFeePerGas: handleUint(fields[3], "maxFeePerGas"),
  184. gasPrice: null,
  185. gasLimit: handleUint(fields[4], "gasLimit"),
  186. to: handleAddress(fields[5]),
  187. value: handleUint(fields[6], "value"),
  188. data: hexlify(fields[7]),
  189. accessList: handleAccessList(fields[8], "accessList"),
  190. };
  191. // Unsigned EIP-1559 Transaction
  192. if (fields.length === 9) {
  193. return tx;
  194. }
  195. //tx.hash = keccak256(data);
  196. _parseEipSignature(tx, fields.slice(9));
  197. return tx;
  198. }
  199. function _serializeEip1559(tx, sig) {
  200. const fields = [
  201. formatNumber(tx.chainId, "chainId"),
  202. formatNumber(tx.nonce, "nonce"),
  203. formatNumber(tx.maxPriorityFeePerGas || 0, "maxPriorityFeePerGas"),
  204. formatNumber(tx.maxFeePerGas || 0, "maxFeePerGas"),
  205. formatNumber(tx.gasLimit, "gasLimit"),
  206. (tx.to || "0x"),
  207. formatNumber(tx.value, "value"),
  208. tx.data,
  209. formatAccessList(tx.accessList || [])
  210. ];
  211. if (sig) {
  212. fields.push(formatNumber(sig.yParity, "yParity"));
  213. fields.push(toBeArray(sig.r));
  214. fields.push(toBeArray(sig.s));
  215. }
  216. return concat(["0x02", encodeRlp(fields)]);
  217. }
  218. function _parseEip2930(data) {
  219. const fields = decodeRlp(getBytes(data).slice(1));
  220. assertArgument(Array.isArray(fields) && (fields.length === 8 || fields.length === 11), "invalid field count for transaction type: 1", "data", hexlify(data));
  221. const tx = {
  222. type: 1,
  223. chainId: handleUint(fields[0], "chainId"),
  224. nonce: handleNumber(fields[1], "nonce"),
  225. gasPrice: handleUint(fields[2], "gasPrice"),
  226. gasLimit: handleUint(fields[3], "gasLimit"),
  227. to: handleAddress(fields[4]),
  228. value: handleUint(fields[5], "value"),
  229. data: hexlify(fields[6]),
  230. accessList: handleAccessList(fields[7], "accessList")
  231. };
  232. // Unsigned EIP-2930 Transaction
  233. if (fields.length === 8) {
  234. return tx;
  235. }
  236. //tx.hash = keccak256(data);
  237. _parseEipSignature(tx, fields.slice(8));
  238. return tx;
  239. }
  240. function _serializeEip2930(tx, sig) {
  241. const fields = [
  242. formatNumber(tx.chainId, "chainId"),
  243. formatNumber(tx.nonce, "nonce"),
  244. formatNumber(tx.gasPrice || 0, "gasPrice"),
  245. formatNumber(tx.gasLimit, "gasLimit"),
  246. (tx.to || "0x"),
  247. formatNumber(tx.value, "value"),
  248. tx.data,
  249. formatAccessList(tx.accessList || [])
  250. ];
  251. if (sig) {
  252. fields.push(formatNumber(sig.yParity, "recoveryParam"));
  253. fields.push(toBeArray(sig.r));
  254. fields.push(toBeArray(sig.s));
  255. }
  256. return concat(["0x01", encodeRlp(fields)]);
  257. }
  258. function _parseEip4844(data) {
  259. let fields = decodeRlp(getBytes(data).slice(1));
  260. let typeName = "3";
  261. let blobs = null;
  262. // Parse the network format
  263. if (fields.length === 4 && Array.isArray(fields[0])) {
  264. typeName = "3 (network format)";
  265. const fBlobs = fields[1], fCommits = fields[2], fProofs = fields[3];
  266. assertArgument(Array.isArray(fBlobs), "invalid network format: blobs not an array", "fields[1]", fBlobs);
  267. assertArgument(Array.isArray(fCommits), "invalid network format: commitments not an array", "fields[2]", fCommits);
  268. assertArgument(Array.isArray(fProofs), "invalid network format: proofs not an array", "fields[3]", fProofs);
  269. assertArgument(fBlobs.length === fCommits.length, "invalid network format: blobs/commitments length mismatch", "fields", fields);
  270. assertArgument(fBlobs.length === fProofs.length, "invalid network format: blobs/proofs length mismatch", "fields", fields);
  271. blobs = [];
  272. for (let i = 0; i < fields[1].length; i++) {
  273. blobs.push({
  274. data: fBlobs[i],
  275. commitment: fCommits[i],
  276. proof: fProofs[i],
  277. });
  278. }
  279. fields = fields[0];
  280. }
  281. assertArgument(Array.isArray(fields) && (fields.length === 11 || fields.length === 14), `invalid field count for transaction type: ${typeName}`, "data", hexlify(data));
  282. const tx = {
  283. type: 3,
  284. chainId: handleUint(fields[0], "chainId"),
  285. nonce: handleNumber(fields[1], "nonce"),
  286. maxPriorityFeePerGas: handleUint(fields[2], "maxPriorityFeePerGas"),
  287. maxFeePerGas: handleUint(fields[3], "maxFeePerGas"),
  288. gasPrice: null,
  289. gasLimit: handleUint(fields[4], "gasLimit"),
  290. to: handleAddress(fields[5]),
  291. value: handleUint(fields[6], "value"),
  292. data: hexlify(fields[7]),
  293. accessList: handleAccessList(fields[8], "accessList"),
  294. maxFeePerBlobGas: handleUint(fields[9], "maxFeePerBlobGas"),
  295. blobVersionedHashes: fields[10]
  296. };
  297. if (blobs) {
  298. tx.blobs = blobs;
  299. }
  300. assertArgument(tx.to != null, `invalid address for transaction type: ${typeName}`, "data", data);
  301. assertArgument(Array.isArray(tx.blobVersionedHashes), "invalid blobVersionedHashes: must be an array", "data", data);
  302. for (let i = 0; i < tx.blobVersionedHashes.length; i++) {
  303. assertArgument(isHexString(tx.blobVersionedHashes[i], 32), `invalid blobVersionedHash at index ${i}: must be length 32`, "data", data);
  304. }
  305. // Unsigned EIP-4844 Transaction
  306. if (fields.length === 11) {
  307. return tx;
  308. }
  309. // @TODO: Do we need to do this? This is only called internally
  310. // and used to verify hashes; it might save time to not do this
  311. //tx.hash = keccak256(concat([ "0x03", encodeRlp(fields) ]));
  312. _parseEipSignature(tx, fields.slice(11));
  313. return tx;
  314. }
  315. function _serializeEip4844(tx, sig, blobs) {
  316. const fields = [
  317. formatNumber(tx.chainId, "chainId"),
  318. formatNumber(tx.nonce, "nonce"),
  319. formatNumber(tx.maxPriorityFeePerGas || 0, "maxPriorityFeePerGas"),
  320. formatNumber(tx.maxFeePerGas || 0, "maxFeePerGas"),
  321. formatNumber(tx.gasLimit, "gasLimit"),
  322. (tx.to || ZeroAddress),
  323. formatNumber(tx.value, "value"),
  324. tx.data,
  325. formatAccessList(tx.accessList || []),
  326. formatNumber(tx.maxFeePerBlobGas || 0, "maxFeePerBlobGas"),
  327. formatHashes(tx.blobVersionedHashes || [], "blobVersionedHashes")
  328. ];
  329. if (sig) {
  330. fields.push(formatNumber(sig.yParity, "yParity"));
  331. fields.push(toBeArray(sig.r));
  332. fields.push(toBeArray(sig.s));
  333. // We have blobs; return the network wrapped format
  334. if (blobs) {
  335. return concat([
  336. "0x03",
  337. encodeRlp([
  338. fields,
  339. blobs.map((b) => b.data),
  340. blobs.map((b) => b.commitment),
  341. blobs.map((b) => b.proof),
  342. ])
  343. ]);
  344. }
  345. }
  346. return concat(["0x03", encodeRlp(fields)]);
  347. }
  348. /**
  349. * A **Transaction** describes an operation to be executed on
  350. * Ethereum by an Externally Owned Account (EOA). It includes
  351. * who (the [[to]] address), what (the [[data]]) and how much (the
  352. * [[value]] in ether) the operation should entail.
  353. *
  354. * @example:
  355. * tx = new Transaction()
  356. * //_result:
  357. *
  358. * tx.data = "0x1234";
  359. * //_result:
  360. */
  361. export class Transaction {
  362. #type;
  363. #to;
  364. #data;
  365. #nonce;
  366. #gasLimit;
  367. #gasPrice;
  368. #maxPriorityFeePerGas;
  369. #maxFeePerGas;
  370. #value;
  371. #chainId;
  372. #sig;
  373. #accessList;
  374. #maxFeePerBlobGas;
  375. #blobVersionedHashes;
  376. #kzg;
  377. #blobs;
  378. /**
  379. * The transaction type.
  380. *
  381. * If null, the type will be automatically inferred based on
  382. * explicit properties.
  383. */
  384. get type() { return this.#type; }
  385. set type(value) {
  386. switch (value) {
  387. case null:
  388. this.#type = null;
  389. break;
  390. case 0:
  391. case "legacy":
  392. this.#type = 0;
  393. break;
  394. case 1:
  395. case "berlin":
  396. case "eip-2930":
  397. this.#type = 1;
  398. break;
  399. case 2:
  400. case "london":
  401. case "eip-1559":
  402. this.#type = 2;
  403. break;
  404. case 3:
  405. case "cancun":
  406. case "eip-4844":
  407. this.#type = 3;
  408. break;
  409. default:
  410. assertArgument(false, "unsupported transaction type", "type", value);
  411. }
  412. }
  413. /**
  414. * The name of the transaction type.
  415. */
  416. get typeName() {
  417. switch (this.type) {
  418. case 0: return "legacy";
  419. case 1: return "eip-2930";
  420. case 2: return "eip-1559";
  421. case 3: return "eip-4844";
  422. }
  423. return null;
  424. }
  425. /**
  426. * The ``to`` address for the transaction or ``null`` if the
  427. * transaction is an ``init`` transaction.
  428. */
  429. get to() {
  430. const value = this.#to;
  431. if (value == null && this.type === 3) {
  432. return ZeroAddress;
  433. }
  434. return value;
  435. }
  436. set to(value) {
  437. this.#to = (value == null) ? null : getAddress(value);
  438. }
  439. /**
  440. * The transaction nonce.
  441. */
  442. get nonce() { return this.#nonce; }
  443. set nonce(value) { this.#nonce = getNumber(value, "value"); }
  444. /**
  445. * The gas limit.
  446. */
  447. get gasLimit() { return this.#gasLimit; }
  448. set gasLimit(value) { this.#gasLimit = getBigInt(value); }
  449. /**
  450. * The gas price.
  451. *
  452. * On legacy networks this defines the fee that will be paid. On
  453. * EIP-1559 networks, this should be ``null``.
  454. */
  455. get gasPrice() {
  456. const value = this.#gasPrice;
  457. if (value == null && (this.type === 0 || this.type === 1)) {
  458. return BN_0;
  459. }
  460. return value;
  461. }
  462. set gasPrice(value) {
  463. this.#gasPrice = (value == null) ? null : getBigInt(value, "gasPrice");
  464. }
  465. /**
  466. * The maximum priority fee per unit of gas to pay. On legacy
  467. * networks this should be ``null``.
  468. */
  469. get maxPriorityFeePerGas() {
  470. const value = this.#maxPriorityFeePerGas;
  471. if (value == null) {
  472. if (this.type === 2 || this.type === 3) {
  473. return BN_0;
  474. }
  475. return null;
  476. }
  477. return value;
  478. }
  479. set maxPriorityFeePerGas(value) {
  480. this.#maxPriorityFeePerGas = (value == null) ? null : getBigInt(value, "maxPriorityFeePerGas");
  481. }
  482. /**
  483. * The maximum total fee per unit of gas to pay. On legacy
  484. * networks this should be ``null``.
  485. */
  486. get maxFeePerGas() {
  487. const value = this.#maxFeePerGas;
  488. if (value == null) {
  489. if (this.type === 2 || this.type === 3) {
  490. return BN_0;
  491. }
  492. return null;
  493. }
  494. return value;
  495. }
  496. set maxFeePerGas(value) {
  497. this.#maxFeePerGas = (value == null) ? null : getBigInt(value, "maxFeePerGas");
  498. }
  499. /**
  500. * The transaction data. For ``init`` transactions this is the
  501. * deployment code.
  502. */
  503. get data() { return this.#data; }
  504. set data(value) { this.#data = hexlify(value); }
  505. /**
  506. * The amount of ether (in wei) to send in this transactions.
  507. */
  508. get value() { return this.#value; }
  509. set value(value) {
  510. this.#value = getBigInt(value, "value");
  511. }
  512. /**
  513. * The chain ID this transaction is valid on.
  514. */
  515. get chainId() { return this.#chainId; }
  516. set chainId(value) { this.#chainId = getBigInt(value); }
  517. /**
  518. * If signed, the signature for this transaction.
  519. */
  520. get signature() { return this.#sig || null; }
  521. set signature(value) {
  522. this.#sig = (value == null) ? null : Signature.from(value);
  523. }
  524. /**
  525. * The access list.
  526. *
  527. * An access list permits discounted (but pre-paid) access to
  528. * bytecode and state variable access within contract execution.
  529. */
  530. get accessList() {
  531. const value = this.#accessList || null;
  532. if (value == null) {
  533. if (this.type === 1 || this.type === 2 || this.type === 3) {
  534. // @TODO: in v7, this should assign the value or become
  535. // a live object itself, otherwise mutation is inconsistent
  536. return [];
  537. }
  538. return null;
  539. }
  540. return value;
  541. }
  542. set accessList(value) {
  543. this.#accessList = (value == null) ? null : accessListify(value);
  544. }
  545. /**
  546. * The max fee per blob gas for Cancun transactions.
  547. */
  548. get maxFeePerBlobGas() {
  549. const value = this.#maxFeePerBlobGas;
  550. if (value == null && this.type === 3) {
  551. return BN_0;
  552. }
  553. return value;
  554. }
  555. set maxFeePerBlobGas(value) {
  556. this.#maxFeePerBlobGas = (value == null) ? null : getBigInt(value, "maxFeePerBlobGas");
  557. }
  558. /**
  559. * The BLOb versioned hashes for Cancun transactions.
  560. */
  561. get blobVersionedHashes() {
  562. // @TODO: Mutation is inconsistent; if unset, the returned value
  563. // cannot mutate the object, if set it can
  564. let value = this.#blobVersionedHashes;
  565. if (value == null && this.type === 3) {
  566. return [];
  567. }
  568. return value;
  569. }
  570. set blobVersionedHashes(value) {
  571. if (value != null) {
  572. assertArgument(Array.isArray(value), "blobVersionedHashes must be an Array", "value", value);
  573. value = value.slice();
  574. for (let i = 0; i < value.length; i++) {
  575. assertArgument(isHexString(value[i], 32), "invalid blobVersionedHash", `value[${i}]`, value[i]);
  576. }
  577. }
  578. this.#blobVersionedHashes = value;
  579. }
  580. /**
  581. * The BLObs for the Transaction, if any.
  582. *
  583. * If ``blobs`` is non-``null``, then the [[seriailized]]
  584. * will return the network formatted sidecar, otherwise it
  585. * will return the standard [[link-eip-2718]] payload. The
  586. * [[unsignedSerialized]] is unaffected regardless.
  587. *
  588. * When setting ``blobs``, either fully valid [[Blob]] objects
  589. * may be specified (i.e. correctly padded, with correct
  590. * committments and proofs) or a raw [[BytesLike]] may
  591. * be provided.
  592. *
  593. * If raw [[BytesLike]] are provided, the [[kzg]] property **must**
  594. * be already set. The blob will be correctly padded and the
  595. * [[KzgLibrary]] will be used to compute the committment and
  596. * proof for the blob.
  597. *
  598. * A BLOb is a sequence of field elements, each of which must
  599. * be within the BLS field modulo, so some additional processing
  600. * may be required to encode arbitrary data to ensure each 32 byte
  601. * field is within the valid range.
  602. *
  603. * Setting this automatically populates [[blobVersionedHashes]],
  604. * overwriting any existing values. Setting this to ``null``
  605. * does **not** remove the [[blobVersionedHashes]], leaving them
  606. * present.
  607. */
  608. get blobs() {
  609. if (this.#blobs == null) {
  610. return null;
  611. }
  612. return this.#blobs.map((b) => Object.assign({}, b));
  613. }
  614. set blobs(_blobs) {
  615. if (_blobs == null) {
  616. this.#blobs = null;
  617. return;
  618. }
  619. const blobs = [];
  620. const versionedHashes = [];
  621. for (let i = 0; i < _blobs.length; i++) {
  622. const blob = _blobs[i];
  623. if (isBytesLike(blob)) {
  624. assert(this.#kzg, "adding a raw blob requires a KZG library", "UNSUPPORTED_OPERATION", {
  625. operation: "set blobs()"
  626. });
  627. let data = getBytes(blob);
  628. assertArgument(data.length <= BLOB_SIZE, "blob is too large", `blobs[${i}]`, blob);
  629. // Pad blob if necessary
  630. if (data.length !== BLOB_SIZE) {
  631. const padded = new Uint8Array(BLOB_SIZE);
  632. padded.set(data);
  633. data = padded;
  634. }
  635. const commit = this.#kzg.blobToKzgCommitment(data);
  636. const proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));
  637. blobs.push({
  638. data: hexlify(data),
  639. commitment: hexlify(commit),
  640. proof
  641. });
  642. versionedHashes.push(getVersionedHash(1, commit));
  643. }
  644. else {
  645. const commit = hexlify(blob.commitment);
  646. blobs.push({
  647. data: hexlify(blob.data),
  648. commitment: commit,
  649. proof: hexlify(blob.proof)
  650. });
  651. versionedHashes.push(getVersionedHash(1, commit));
  652. }
  653. }
  654. this.#blobs = blobs;
  655. this.#blobVersionedHashes = versionedHashes;
  656. }
  657. get kzg() { return this.#kzg; }
  658. set kzg(kzg) {
  659. this.#kzg = kzg;
  660. }
  661. /**
  662. * Creates a new Transaction with default values.
  663. */
  664. constructor() {
  665. this.#type = null;
  666. this.#to = null;
  667. this.#nonce = 0;
  668. this.#gasLimit = BN_0;
  669. this.#gasPrice = null;
  670. this.#maxPriorityFeePerGas = null;
  671. this.#maxFeePerGas = null;
  672. this.#data = "0x";
  673. this.#value = BN_0;
  674. this.#chainId = BN_0;
  675. this.#sig = null;
  676. this.#accessList = null;
  677. this.#maxFeePerBlobGas = null;
  678. this.#blobVersionedHashes = null;
  679. this.#blobs = null;
  680. this.#kzg = null;
  681. }
  682. /**
  683. * The transaction hash, if signed. Otherwise, ``null``.
  684. */
  685. get hash() {
  686. if (this.signature == null) {
  687. return null;
  688. }
  689. return keccak256(this.#getSerialized(true, false));
  690. }
  691. /**
  692. * The pre-image hash of this transaction.
  693. *
  694. * This is the digest that a [[Signer]] must sign to authorize
  695. * this transaction.
  696. */
  697. get unsignedHash() {
  698. return keccak256(this.unsignedSerialized);
  699. }
  700. /**
  701. * The sending address, if signed. Otherwise, ``null``.
  702. */
  703. get from() {
  704. if (this.signature == null) {
  705. return null;
  706. }
  707. return recoverAddress(this.unsignedHash, this.signature);
  708. }
  709. /**
  710. * The public key of the sender, if signed. Otherwise, ``null``.
  711. */
  712. get fromPublicKey() {
  713. if (this.signature == null) {
  714. return null;
  715. }
  716. return SigningKey.recoverPublicKey(this.unsignedHash, this.signature);
  717. }
  718. /**
  719. * Returns true if signed.
  720. *
  721. * This provides a Type Guard that properties requiring a signed
  722. * transaction are non-null.
  723. */
  724. isSigned() {
  725. return this.signature != null;
  726. }
  727. #getSerialized(signed, sidecar) {
  728. assert(!signed || this.signature != null, "cannot serialize unsigned transaction; maybe you meant .unsignedSerialized", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
  729. const sig = signed ? this.signature : null;
  730. switch (this.inferType()) {
  731. case 0:
  732. return _serializeLegacy(this, sig);
  733. case 1:
  734. return _serializeEip2930(this, sig);
  735. case 2:
  736. return _serializeEip1559(this, sig);
  737. case 3:
  738. return _serializeEip4844(this, sig, sidecar ? this.blobs : null);
  739. }
  740. assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
  741. }
  742. /**
  743. * The serialized transaction.
  744. *
  745. * This throws if the transaction is unsigned. For the pre-image,
  746. * use [[unsignedSerialized]].
  747. */
  748. get serialized() {
  749. return this.#getSerialized(true, true);
  750. }
  751. /**
  752. * The transaction pre-image.
  753. *
  754. * The hash of this is the digest which needs to be signed to
  755. * authorize this transaction.
  756. */
  757. get unsignedSerialized() {
  758. return this.#getSerialized(false, false);
  759. }
  760. /**
  761. * Return the most "likely" type; currently the highest
  762. * supported transaction type.
  763. */
  764. inferType() {
  765. const types = this.inferTypes();
  766. // Prefer London (EIP-1559) over Cancun (BLOb)
  767. if (types.indexOf(2) >= 0) {
  768. return 2;
  769. }
  770. // Return the highest inferred type
  771. return (types.pop());
  772. }
  773. /**
  774. * Validates the explicit properties and returns a list of compatible
  775. * transaction types.
  776. */
  777. inferTypes() {
  778. // Checks that there are no conflicting properties set
  779. const hasGasPrice = this.gasPrice != null;
  780. const hasFee = (this.maxFeePerGas != null || this.maxPriorityFeePerGas != null);
  781. const hasAccessList = (this.accessList != null);
  782. const hasBlob = (this.#maxFeePerBlobGas != null || this.#blobVersionedHashes);
  783. //if (hasGasPrice && hasFee) {
  784. // throw new Error("transaction cannot have gasPrice and maxFeePerGas");
  785. //}
  786. if (this.maxFeePerGas != null && this.maxPriorityFeePerGas != null) {
  787. assert(this.maxFeePerGas >= this.maxPriorityFeePerGas, "priorityFee cannot be more than maxFee", "BAD_DATA", { value: this });
  788. }
  789. //if (this.type === 2 && hasGasPrice) {
  790. // throw new Error("eip-1559 transaction cannot have gasPrice");
  791. //}
  792. assert(!hasFee || (this.type !== 0 && this.type !== 1), "transaction type cannot have maxFeePerGas or maxPriorityFeePerGas", "BAD_DATA", { value: this });
  793. assert(this.type !== 0 || !hasAccessList, "legacy transaction cannot have accessList", "BAD_DATA", { value: this });
  794. const types = [];
  795. // Explicit type
  796. if (this.type != null) {
  797. types.push(this.type);
  798. }
  799. else {
  800. if (hasFee) {
  801. types.push(2);
  802. }
  803. else if (hasGasPrice) {
  804. types.push(1);
  805. if (!hasAccessList) {
  806. types.push(0);
  807. }
  808. }
  809. else if (hasAccessList) {
  810. types.push(1);
  811. types.push(2);
  812. }
  813. else if (hasBlob && this.to) {
  814. types.push(3);
  815. }
  816. else {
  817. types.push(0);
  818. types.push(1);
  819. types.push(2);
  820. types.push(3);
  821. }
  822. }
  823. types.sort();
  824. return types;
  825. }
  826. /**
  827. * Returns true if this transaction is a legacy transaction (i.e.
  828. * ``type === 0``).
  829. *
  830. * This provides a Type Guard that the related properties are
  831. * non-null.
  832. */
  833. isLegacy() {
  834. return (this.type === 0);
  835. }
  836. /**
  837. * Returns true if this transaction is berlin hardform transaction (i.e.
  838. * ``type === 1``).
  839. *
  840. * This provides a Type Guard that the related properties are
  841. * non-null.
  842. */
  843. isBerlin() {
  844. return (this.type === 1);
  845. }
  846. /**
  847. * Returns true if this transaction is london hardform transaction (i.e.
  848. * ``type === 2``).
  849. *
  850. * This provides a Type Guard that the related properties are
  851. * non-null.
  852. */
  853. isLondon() {
  854. return (this.type === 2);
  855. }
  856. /**
  857. * Returns true if this transaction is an [[link-eip-4844]] BLOB
  858. * transaction.
  859. *
  860. * This provides a Type Guard that the related properties are
  861. * non-null.
  862. */
  863. isCancun() {
  864. return (this.type === 3);
  865. }
  866. /**
  867. * Create a copy of this transaciton.
  868. */
  869. clone() {
  870. return Transaction.from(this);
  871. }
  872. /**
  873. * Return a JSON-friendly object.
  874. */
  875. toJSON() {
  876. const s = (v) => {
  877. if (v == null) {
  878. return null;
  879. }
  880. return v.toString();
  881. };
  882. return {
  883. type: this.type,
  884. to: this.to,
  885. // from: this.from,
  886. data: this.data,
  887. nonce: this.nonce,
  888. gasLimit: s(this.gasLimit),
  889. gasPrice: s(this.gasPrice),
  890. maxPriorityFeePerGas: s(this.maxPriorityFeePerGas),
  891. maxFeePerGas: s(this.maxFeePerGas),
  892. value: s(this.value),
  893. chainId: s(this.chainId),
  894. sig: this.signature ? this.signature.toJSON() : null,
  895. accessList: this.accessList
  896. };
  897. }
  898. /**
  899. * Create a **Transaction** from a serialized transaction or a
  900. * Transaction-like object.
  901. */
  902. static from(tx) {
  903. if (tx == null) {
  904. return new Transaction();
  905. }
  906. if (typeof (tx) === "string") {
  907. const payload = getBytes(tx);
  908. if (payload[0] >= 0x7f) { // @TODO: > vs >= ??
  909. return Transaction.from(_parseLegacy(payload));
  910. }
  911. switch (payload[0]) {
  912. case 1: return Transaction.from(_parseEip2930(payload));
  913. case 2: return Transaction.from(_parseEip1559(payload));
  914. case 3: return Transaction.from(_parseEip4844(payload));
  915. }
  916. assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: "from" });
  917. }
  918. const result = new Transaction();
  919. if (tx.type != null) {
  920. result.type = tx.type;
  921. }
  922. if (tx.to != null) {
  923. result.to = tx.to;
  924. }
  925. if (tx.nonce != null) {
  926. result.nonce = tx.nonce;
  927. }
  928. if (tx.gasLimit != null) {
  929. result.gasLimit = tx.gasLimit;
  930. }
  931. if (tx.gasPrice != null) {
  932. result.gasPrice = tx.gasPrice;
  933. }
  934. if (tx.maxPriorityFeePerGas != null) {
  935. result.maxPriorityFeePerGas = tx.maxPriorityFeePerGas;
  936. }
  937. if (tx.maxFeePerGas != null) {
  938. result.maxFeePerGas = tx.maxFeePerGas;
  939. }
  940. if (tx.maxFeePerBlobGas != null) {
  941. result.maxFeePerBlobGas = tx.maxFeePerBlobGas;
  942. }
  943. if (tx.data != null) {
  944. result.data = tx.data;
  945. }
  946. if (tx.value != null) {
  947. result.value = tx.value;
  948. }
  949. if (tx.chainId != null) {
  950. result.chainId = tx.chainId;
  951. }
  952. if (tx.signature != null) {
  953. result.signature = Signature.from(tx.signature);
  954. }
  955. if (tx.accessList != null) {
  956. result.accessList = tx.accessList;
  957. }
  958. // This will get overwritten by blobs, if present
  959. if (tx.blobVersionedHashes != null) {
  960. result.blobVersionedHashes = tx.blobVersionedHashes;
  961. }
  962. // Make sure we assign the kzg before assigning blobs, which
  963. // require the library in the event raw blob data is provided.
  964. if (tx.kzg != null) {
  965. result.kzg = tx.kzg;
  966. }
  967. if (tx.blobs != null) {
  968. result.blobs = tx.blobs;
  969. }
  970. if (tx.hash != null) {
  971. assertArgument(result.isSigned(), "unsigned transaction cannot define '.hash'", "tx", tx);
  972. assertArgument(result.hash === tx.hash, "hash mismatch", "tx", tx);
  973. }
  974. if (tx.from != null) {
  975. assertArgument(result.isSigned(), "unsigned transaction cannot define '.from'", "tx", tx);
  976. assertArgument(result.from.toLowerCase() === (tx.from || "").toLowerCase(), "from mismatch", "tx", tx);
  977. }
  978. return result;
  979. }
  980. }
  981. //# sourceMappingURL=transaction.js.map