Added EIP-4844 broadcast support.
This commit is contained in:
parent
12772e9498
commit
92bad88261
@ -93,7 +93,7 @@
|
|||||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"gitHead": "556fdd91d9b6bf7db4041bb099e66b2080e1a985",
|
"gitHead": "12772e9498b70f8538838f30e16f3792ea90e173",
|
||||||
"homepage": "https://ethers.org",
|
"homepage": "https://ethers.org",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ethereum",
|
"ethereum",
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"name": "ethers",
|
"name": "ethers",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public",
|
"access": "public",
|
||||||
"tag": "latest"
|
"tag": "next"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -131,5 +131,5 @@
|
|||||||
"test-esm": "mocha --trace-warnings --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
"test-esm": "mocha --trace-warnings --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"version": "6.11.1"
|
"version": "6.12.0-beta.1"
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
/**
|
/**
|
||||||
* The current version of Ethers.
|
* The current version of Ethers.
|
||||||
*/
|
*/
|
||||||
export const version: string = "6.11.1";
|
export const version: string = "6.12.0-beta.1";
|
||||||
|
@ -177,6 +177,7 @@ export type {
|
|||||||
|
|
||||||
export type {
|
export type {
|
||||||
AccessList, AccessListish, AccessListEntry,
|
AccessList, AccessListish, AccessListEntry,
|
||||||
|
Blob, BlobLike, KzgLibrary,
|
||||||
TransactionLike
|
TransactionLike
|
||||||
} from "./transaction/index.js";
|
} from "./transaction/index.js";
|
||||||
|
|
||||||
|
@ -194,8 +194,8 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
|
|||||||
operation: "signer.getFeeData" });
|
operation: "signer.getFeeData" });
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (pop.type === 2) {
|
} else if (pop.type === 2 || pop.type === 3) {
|
||||||
// Explicitly using EIP-1559
|
// Explicitly using EIP-1559 or EIP-4844
|
||||||
|
|
||||||
// Populate missing fee data
|
// Populate missing fee data
|
||||||
if (pop.maxFeePerGas == null) {
|
if (pop.maxFeePerGas == null) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
//import { resolveAddress } from "@ethersproject/address";
|
//import { resolveAddress } from "@ethersproject/address";
|
||||||
import {
|
import {
|
||||||
defineProperties, getBigInt, getNumber, hexlify, resolveProperties,
|
defineProperties, getBigInt, getNumber, hexlify, isBytesLike,
|
||||||
|
resolveProperties,
|
||||||
assert, assertArgument, isError, makeError
|
assert, assertArgument, isError, makeError
|
||||||
} from "../utils/index.js";
|
} from "../utils/index.js";
|
||||||
import { accessListify } from "../transaction/index.js";
|
import { accessListify } from "../transaction/index.js";
|
||||||
@ -8,7 +9,9 @@ import { accessListify } from "../transaction/index.js";
|
|||||||
import type { AddressLike, NameResolver } from "../address/index.js";
|
import type { AddressLike, NameResolver } from "../address/index.js";
|
||||||
import type { BigNumberish, EventEmitterable } from "../utils/index.js";
|
import type { BigNumberish, EventEmitterable } from "../utils/index.js";
|
||||||
import type { Signature } from "../crypto/index.js";
|
import type { Signature } from "../crypto/index.js";
|
||||||
import type { AccessList, AccessListish, TransactionLike } from "../transaction/index.js";
|
import type {
|
||||||
|
AccessList, AccessListish, BlobLike, KzgLibrary, TransactionLike
|
||||||
|
} from "../transaction/index.js";
|
||||||
|
|
||||||
import type { ContractRunner } from "./contracts.js";
|
import type { ContractRunner } from "./contracts.js";
|
||||||
import type { Network } from "./network.js";
|
import type { Network } from "./network.js";
|
||||||
@ -214,6 +217,30 @@ export interface TransactionRequest {
|
|||||||
*/
|
*/
|
||||||
enableCcipRead?: boolean;
|
enableCcipRead?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The blob versioned hashes (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
blobVersionedHashes?: null | Array<string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum fee per blob gas (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
maxFeePerBlobGas?: null | BigNumberish;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Any blobs to include in the transaction (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
blobs?: null | Array<BlobLike>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An external library for computing the KZG commitments and
|
||||||
|
* proofs necessary for EIP-4844 transactions (see [[link-eip-4844]]).
|
||||||
|
*
|
||||||
|
* This is generally ``null``, unless you are creating BLOb
|
||||||
|
* transactions.
|
||||||
|
*/
|
||||||
|
kzg?: null | KzgLibrary;
|
||||||
|
|
||||||
// Todo?
|
// Todo?
|
||||||
//gasMultiplier?: number;
|
//gasMultiplier?: number;
|
||||||
};
|
};
|
||||||
@ -332,7 +359,7 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest
|
|||||||
|
|
||||||
if (req.data) { result.data = hexlify(req.data); }
|
if (req.data) { result.data = hexlify(req.data); }
|
||||||
|
|
||||||
const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerGas,maxPriorityFeePerGas,value".split(/,/);
|
const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerBlobGas,maxFeePerGas,maxPriorityFeePerGas,value".split(/,/);
|
||||||
for (const key of bigIntKeys) {
|
for (const key of bigIntKeys) {
|
||||||
if (!(key in req) || (<any>req)[key] == null) { continue; }
|
if (!(key in req) || (<any>req)[key] == null) { continue; }
|
||||||
result[key] = getBigInt((<any>req)[key], `request.${ key }`);
|
result[key] = getBigInt((<any>req)[key], `request.${ key }`);
|
||||||
@ -358,6 +385,19 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest
|
|||||||
result.customData = req.customData;
|
result.customData = req.customData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("blobVersionedHashes" in req && req.blobVersionedHashes) {
|
||||||
|
result.blobVersionedHashes = req.blobVersionedHashes.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("kzg" in req) { result.kzg = req.kzg; }
|
||||||
|
|
||||||
|
if ("blobs" in req && req.blobs) {
|
||||||
|
result.blobs = req.blobs.map((b) => {
|
||||||
|
if (isBytesLike(b)) { return hexlify(b); }
|
||||||
|
return Object.assign({ }, b);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,4 +28,6 @@ export { accessListify } from "./accesslist.js";
|
|||||||
export { computeAddress, recoverAddress } from "./address.js";
|
export { computeAddress, recoverAddress } from "./address.js";
|
||||||
export { Transaction } from "./transaction.js";
|
export { Transaction } from "./transaction.js";
|
||||||
|
|
||||||
export type { TransactionLike } from "./transaction.js";
|
export type {
|
||||||
|
Blob, BlobLike, KzgLibrary, TransactionLike
|
||||||
|
} from "./transaction.js";
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
import { getAddress } from "../address/index.js";
|
import { getAddress } from "../address/index.js";
|
||||||
import { ZeroAddress } from "../constants/addresses.js";
|
import { ZeroAddress } from "../constants/addresses.js";
|
||||||
import { keccak256, Signature, SigningKey } from "../crypto/index.js";
|
import {
|
||||||
|
keccak256, sha256, Signature, SigningKey
|
||||||
|
} from "../crypto/index.js";
|
||||||
import {
|
import {
|
||||||
concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
|
concat, decodeRlp, encodeRlp, getBytes, getBigInt, getNumber, hexlify,
|
||||||
assert, assertArgument, isHexString, toBeArray, zeroPadValue
|
assert, assertArgument, isBytesLike, isHexString, toBeArray, zeroPadValue
|
||||||
} from "../utils/index.js";
|
} from "../utils/index.js";
|
||||||
|
|
||||||
import { accessListify } from "./accesslist.js";
|
import { accessListify } from "./accesslist.js";
|
||||||
@ -23,6 +25,7 @@ const BN_28 = BigInt(28)
|
|||||||
const BN_35 = BigInt(35);
|
const BN_35 = BigInt(35);
|
||||||
const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
const BN_MAX_UINT = BigInt("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
|
||||||
|
|
||||||
|
const BLOB_SIZE = 4096 * 32;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A **TransactionLike** is an object which is appropriate as a loose
|
* A **TransactionLike** is an object which is appropriate as a loose
|
||||||
@ -109,6 +112,61 @@ export interface TransactionLike<A = string> {
|
|||||||
* The versioned hashes (see [[link-eip-4844]]).
|
* The versioned hashes (see [[link-eip-4844]]).
|
||||||
*/
|
*/
|
||||||
blobVersionedHashes?: null | Array<string>;
|
blobVersionedHashes?: null | Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The blobs (if any) attached to this transaction (see [[link-eip-4844]]).
|
||||||
|
*/
|
||||||
|
blobs?: null | Array<BlobLike>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An external library for computing the KZG commitments and
|
||||||
|
* proofs necessary for EIP-4844 transactions (see [[link-eip-4844]]).
|
||||||
|
*
|
||||||
|
* This is generally ``null``, unless you are creating BLOb
|
||||||
|
* transactions.
|
||||||
|
*/
|
||||||
|
kzg?: null | KzgLibrary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A full-valid BLOb object for [[link-eip-4844]] transactions.
|
||||||
|
*
|
||||||
|
* The commitment and proof should have been computed using a
|
||||||
|
* KZG library.
|
||||||
|
*/
|
||||||
|
export interface Blob {
|
||||||
|
data: string;
|
||||||
|
proof: string;
|
||||||
|
commitment: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A BLOb object that can be passed for [[link-eip-4844]]
|
||||||
|
* transactions.
|
||||||
|
*
|
||||||
|
* It may have had its commitment and proof already provided
|
||||||
|
* or rely on an attached [[KzgLibrary]] to compute them.
|
||||||
|
*/
|
||||||
|
export type BlobLike = BytesLike | {
|
||||||
|
data: BytesLike;
|
||||||
|
proof: BytesLike;
|
||||||
|
commitment: BytesLike;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A KZG Library with the necessary functions to compute
|
||||||
|
* BLOb commitments and proofs.
|
||||||
|
*/
|
||||||
|
export interface KzgLibrary {
|
||||||
|
blobToKzgCommitment: (blob: Uint8Array) => Uint8Array;
|
||||||
|
computeBlobKzgProof: (blob: Uint8Array, commitment: Uint8Array) => Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionedHash(version: number, hash: BytesLike): string {
|
||||||
|
let versioned = version.toString(16);
|
||||||
|
while (versioned.length < 2) { versioned = "0" + versioned; }
|
||||||
|
versioned += sha256(hash).substring(4);
|
||||||
|
return "0x" + versioned;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddress(value: string): null | string {
|
function handleAddress(value: string): null | string {
|
||||||
@ -199,13 +257,13 @@ function _parseLegacy(data: Uint8Array): TransactionLike {
|
|||||||
v
|
v
|
||||||
});
|
});
|
||||||
|
|
||||||
tx.hash = keccak256(data);
|
//tx.hash = keccak256(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeLegacy(tx: Transaction, sig?: Signature): string {
|
function _serializeLegacy(tx: Transaction, sig: null | Signature): string {
|
||||||
const fields: Array<any> = [
|
const fields: Array<any> = [
|
||||||
formatNumber(tx.nonce, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
formatNumber(tx.gasPrice || 0, "gasPrice"),
|
formatNumber(tx.gasPrice || 0, "gasPrice"),
|
||||||
@ -302,14 +360,14 @@ function _parseEip1559(data: Uint8Array): TransactionLike {
|
|||||||
// Unsigned EIP-1559 Transaction
|
// Unsigned EIP-1559 Transaction
|
||||||
if (fields.length === 9) { return tx; }
|
if (fields.length === 9) { return tx; }
|
||||||
|
|
||||||
tx.hash = keccak256(data);
|
//tx.hash = keccak256(data);
|
||||||
|
|
||||||
_parseEipSignature(tx, fields.slice(9));
|
_parseEipSignature(tx, fields.slice(9));
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeEip1559(tx: Transaction, sig?: Signature): string {
|
function _serializeEip1559(tx: Transaction, sig: null | Signature): string {
|
||||||
const fields: Array<any> = [
|
const fields: Array<any> = [
|
||||||
formatNumber(tx.chainId, "chainId"),
|
formatNumber(tx.chainId, "chainId"),
|
||||||
formatNumber(tx.nonce, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
@ -352,14 +410,14 @@ function _parseEip2930(data: Uint8Array): TransactionLike {
|
|||||||
// Unsigned EIP-2930 Transaction
|
// Unsigned EIP-2930 Transaction
|
||||||
if (fields.length === 8) { return tx; }
|
if (fields.length === 8) { return tx; }
|
||||||
|
|
||||||
tx.hash = keccak256(data);
|
//tx.hash = keccak256(data);
|
||||||
|
|
||||||
_parseEipSignature(tx, fields.slice(8));
|
_parseEipSignature(tx, fields.slice(8));
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeEip2930(tx: Transaction, sig?: Signature): string {
|
function _serializeEip2930(tx: Transaction, sig: null | Signature): string {
|
||||||
const fields: any = [
|
const fields: any = [
|
||||||
formatNumber(tx.chainId, "chainId"),
|
formatNumber(tx.chainId, "chainId"),
|
||||||
formatNumber(tx.nonce, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
@ -381,10 +439,36 @@ function _serializeEip2930(tx: Transaction, sig?: Signature): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _parseEip4844(data: Uint8Array): TransactionLike {
|
function _parseEip4844(data: Uint8Array): TransactionLike {
|
||||||
const fields: any = decodeRlp(getBytes(data).slice(1));
|
let fields: any = decodeRlp(getBytes(data).slice(1));
|
||||||
|
|
||||||
|
let typeName = "3";
|
||||||
|
|
||||||
|
let blobs: null | Array<Blob> = null;
|
||||||
|
|
||||||
|
// Parse the network format
|
||||||
|
if (fields.length === 4 && Array.isArray(fields[0])) {
|
||||||
|
typeName = "3 (network format)";
|
||||||
|
const fBlobs = fields[1], fCommits = fields[2], fProofs = fields[3];
|
||||||
|
assertArgument(Array.isArray(fBlobs), "invalid network format: blobs not an array", "fields[1]", fBlobs);
|
||||||
|
assertArgument(Array.isArray(fCommits), "invalid network format: commitments not an array", "fields[2]", fCommits);
|
||||||
|
assertArgument(Array.isArray(fProofs), "invalid network format: proofs not an array", "fields[3]", fProofs);
|
||||||
|
assertArgument(fBlobs.length === fCommits.length, "invalid network format: blobs/commitments length mismatch", "fields", fields);
|
||||||
|
assertArgument(fBlobs.length === fProofs.length, "invalid network format: blobs/proofs length mismatch", "fields", fields);
|
||||||
|
|
||||||
|
blobs = [ ];
|
||||||
|
for (let i = 0; i < fields[1].length; i++) {
|
||||||
|
blobs.push({
|
||||||
|
data: fBlobs[i],
|
||||||
|
commitment: fCommits[i],
|
||||||
|
proof: fProofs[i],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fields = fields[0];
|
||||||
|
}
|
||||||
|
|
||||||
assertArgument(Array.isArray(fields) && (fields.length === 11 || fields.length === 14),
|
assertArgument(Array.isArray(fields) && (fields.length === 11 || fields.length === 14),
|
||||||
"invalid field count for transaction type: 3", "data", hexlify(data));
|
`invalid field count for transaction type: ${ typeName }`, "data", hexlify(data));
|
||||||
|
|
||||||
const tx: TransactionLike = {
|
const tx: TransactionLike = {
|
||||||
type: 3,
|
type: 3,
|
||||||
@ -402,7 +486,9 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
|
|||||||
blobVersionedHashes: fields[10]
|
blobVersionedHashes: fields[10]
|
||||||
};
|
};
|
||||||
|
|
||||||
assertArgument(tx.to != null, "invalid address for transaction type: 3", "data", data);
|
if (blobs) { tx.blobs = blobs; }
|
||||||
|
|
||||||
|
assertArgument(tx.to != null, `invalid address for transaction type: ${ typeName }`, "data", data);
|
||||||
|
|
||||||
assertArgument(Array.isArray(tx.blobVersionedHashes), "invalid blobVersionedHashes: must be an array", "data", data);
|
assertArgument(Array.isArray(tx.blobVersionedHashes), "invalid blobVersionedHashes: must be an array", "data", data);
|
||||||
for (let i = 0; i < tx.blobVersionedHashes.length; i++) {
|
for (let i = 0; i < tx.blobVersionedHashes.length; i++) {
|
||||||
@ -412,14 +498,16 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
|
|||||||
// Unsigned EIP-4844 Transaction
|
// Unsigned EIP-4844 Transaction
|
||||||
if (fields.length === 11) { return tx; }
|
if (fields.length === 11) { return tx; }
|
||||||
|
|
||||||
tx.hash = keccak256(data);
|
// @TODO: Do we need to do this? This is only called internally
|
||||||
|
// and used to verify hashes; it might save time to not do this
|
||||||
|
//tx.hash = keccak256(concat([ "0x03", encodeRlp(fields) ]));
|
||||||
|
|
||||||
_parseEipSignature(tx, fields.slice(11));
|
_parseEipSignature(tx, fields.slice(11));
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _serializeEip4844(tx: Transaction, sig?: Signature): string {
|
function _serializeEip4844(tx: Transaction, sig: null | Signature, blobs: null | Array<Blob>): string {
|
||||||
const fields: Array<any> = [
|
const fields: Array<any> = [
|
||||||
formatNumber(tx.chainId, "chainId"),
|
formatNumber(tx.chainId, "chainId"),
|
||||||
formatNumber(tx.nonce, "nonce"),
|
formatNumber(tx.nonce, "nonce"),
|
||||||
@ -438,6 +526,20 @@ function _serializeEip4844(tx: Transaction, sig?: Signature): string {
|
|||||||
fields.push(formatNumber(sig.yParity, "yParity"));
|
fields.push(formatNumber(sig.yParity, "yParity"));
|
||||||
fields.push(toBeArray(sig.r));
|
fields.push(toBeArray(sig.r));
|
||||||
fields.push(toBeArray(sig.s));
|
fields.push(toBeArray(sig.s));
|
||||||
|
|
||||||
|
// We have blobs; return the network wrapped format
|
||||||
|
if (blobs) {
|
||||||
|
return concat([
|
||||||
|
"0x03",
|
||||||
|
encodeRlp([
|
||||||
|
fields,
|
||||||
|
blobs.map((b) => b.data),
|
||||||
|
blobs.map((b) => b.commitment),
|
||||||
|
blobs.map((b) => b.proof),
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return concat([ "0x03", encodeRlp(fields)]);
|
return concat([ "0x03", encodeRlp(fields)]);
|
||||||
@ -471,6 +573,8 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
#accessList: null | AccessList;
|
#accessList: null | AccessList;
|
||||||
#maxFeePerBlobGas: null | bigint;
|
#maxFeePerBlobGas: null | bigint;
|
||||||
#blobVersionedHashes: null | Array<string>;
|
#blobVersionedHashes: null | Array<string>;
|
||||||
|
#kzg: null | KzgLibrary;
|
||||||
|
#blobs: null | Array<Blob>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transaction type.
|
* The transaction type.
|
||||||
@ -651,7 +755,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The BLOB versioned hashes for Cancun transactions.
|
* The BLOb versioned hashes for Cancun transactions.
|
||||||
*/
|
*/
|
||||||
get blobVersionedHashes(): null | Array<string> {
|
get blobVersionedHashes(): null | Array<string> {
|
||||||
// @TODO: Mutation is inconsistent; if unset, the returned value
|
// @TODO: Mutation is inconsistent; if unset, the returned value
|
||||||
@ -671,6 +775,89 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
this.#blobVersionedHashes = value;
|
this.#blobVersionedHashes = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BLObs for the Transaction, if any.
|
||||||
|
*
|
||||||
|
* If ``blobs`` is non-``null``, then the [[seriailized]]
|
||||||
|
* will return the network formatted sidecar, otherwise it
|
||||||
|
* will return the standard [[link-eip-2718]] payload. The
|
||||||
|
* [[unsignedSerialized]] is unaffected regardless.
|
||||||
|
*
|
||||||
|
* When setting ``blobs``, either fully valid [[Blob]] objects
|
||||||
|
* may be specified (i.e. correctly padded, with correct
|
||||||
|
* committments and proofs) or a raw [[BytesLike]] may
|
||||||
|
* be provided.
|
||||||
|
*
|
||||||
|
* If raw [[BytesLike]] are provided, the [[kzg]] property **must**
|
||||||
|
* be already set. The blob will be correctly padded and the
|
||||||
|
* [[KzgLibrary]] will be used to compute the committment and
|
||||||
|
* proof for the blob.
|
||||||
|
*
|
||||||
|
* Setting this automatically populates [[blobVersionedHashes]],
|
||||||
|
* overwriting any existing values. Setting this to ``null``
|
||||||
|
* does **not** remove the [[blobVersionedHashes]], leaving them
|
||||||
|
* present.
|
||||||
|
*/
|
||||||
|
get blobs(): null | Array<Blob> {
|
||||||
|
if (this.#blobs == null) { return null; }
|
||||||
|
return this.#blobs.map((b) => Object.assign({ }, b));
|
||||||
|
}
|
||||||
|
set blobs(_blobs: null | Array<BlobLike>) {
|
||||||
|
if (_blobs == null) {
|
||||||
|
this.#blobs = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blobs: Array<Blob> = [ ];
|
||||||
|
const versionedHashes: Array<string> = [ ];
|
||||||
|
for (let i = 0; i < _blobs.length; i++) {
|
||||||
|
const blob = _blobs[i];
|
||||||
|
|
||||||
|
if (isBytesLike(blob)) {
|
||||||
|
assert(this.#kzg, "adding a raw blob requires a KZG library", "UNSUPPORTED_OPERATION", {
|
||||||
|
operation: "set blobs()"
|
||||||
|
});
|
||||||
|
|
||||||
|
let data = getBytes(blob);
|
||||||
|
assertArgument(data.length <= BLOB_SIZE, "blob is too large", `blobs[${ i }]`, blob);
|
||||||
|
|
||||||
|
// Pad blob if necessary
|
||||||
|
if (data.length !== BLOB_SIZE) {
|
||||||
|
const padded = new Uint8Array(BLOB_SIZE);
|
||||||
|
padded.set(data);
|
||||||
|
data = padded;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commit = this.#kzg.blobToKzgCommitment(data);
|
||||||
|
const proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));
|
||||||
|
|
||||||
|
blobs.push({
|
||||||
|
data: hexlify(data),
|
||||||
|
commitment: hexlify(commit),
|
||||||
|
proof
|
||||||
|
});
|
||||||
|
versionedHashes.push(getVersionedHash(1, commit));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const commit = hexlify(blob.commitment);
|
||||||
|
blobs.push({
|
||||||
|
data: hexlify(blob.data),
|
||||||
|
commitment: commit,
|
||||||
|
proof: hexlify(blob.proof)
|
||||||
|
});
|
||||||
|
versionedHashes.push(getVersionedHash(1, commit));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#blobs = blobs;
|
||||||
|
this.#blobVersionedHashes = versionedHashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
get kzg(): null | KzgLibrary { return this.#kzg; }
|
||||||
|
set kzg(kzg: null | KzgLibrary) {
|
||||||
|
this.#kzg = kzg;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Transaction with default values.
|
* Creates a new Transaction with default values.
|
||||||
*/
|
*/
|
||||||
@ -689,6 +876,8 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
this.#accessList = null;
|
this.#accessList = null;
|
||||||
this.#maxFeePerBlobGas = null;
|
this.#maxFeePerBlobGas = null;
|
||||||
this.#blobVersionedHashes = null;
|
this.#blobVersionedHashes = null;
|
||||||
|
this.#blobs = null;
|
||||||
|
this.#kzg = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -696,7 +885,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
*/
|
*/
|
||||||
get hash(): null | string {
|
get hash(): null | string {
|
||||||
if (this.signature == null) { return null; }
|
if (this.signature == null) { return null; }
|
||||||
return keccak256(this.serialized);
|
return keccak256(this.#getSerialized(true, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -735,6 +924,24 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
return this.signature != null;
|
return this.signature != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#getSerialized(signed: boolean, sidecar: boolean): string {
|
||||||
|
assert(!signed || this.signature != null, "cannot serialize unsigned transaction; maybe you meant .unsignedSerialized", "UNSUPPORTED_OPERATION", { operation: ".serialized"});
|
||||||
|
|
||||||
|
const sig = signed ? this.signature: null;
|
||||||
|
switch (this.inferType()) {
|
||||||
|
case 0:
|
||||||
|
return _serializeLegacy(this, sig);
|
||||||
|
case 1:
|
||||||
|
return _serializeEip2930(this, sig);
|
||||||
|
case 2:
|
||||||
|
return _serializeEip1559(this, sig);
|
||||||
|
case 3:
|
||||||
|
return _serializeEip4844(this, sig, sidecar ? this.blobs: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The serialized transaction.
|
* The serialized transaction.
|
||||||
*
|
*
|
||||||
@ -742,20 +949,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* use [[unsignedSerialized]].
|
* use [[unsignedSerialized]].
|
||||||
*/
|
*/
|
||||||
get serialized(): string {
|
get serialized(): string {
|
||||||
assert(this.signature != null, "cannot serialize unsigned transaction; maybe you meant .unsignedSerialized", "UNSUPPORTED_OPERATION", { operation: ".serialized"});
|
return this.#getSerialized(true, true);
|
||||||
|
|
||||||
switch (this.inferType()) {
|
|
||||||
case 0:
|
|
||||||
return _serializeLegacy(this, this.signature);
|
|
||||||
case 1:
|
|
||||||
return _serializeEip2930(this, this.signature);
|
|
||||||
case 2:
|
|
||||||
return _serializeEip1559(this, this.signature);
|
|
||||||
case 3:
|
|
||||||
return _serializeEip4844(this, this.signature);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".serialized" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -765,18 +959,7 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
* authorize this transaction.
|
* authorize this transaction.
|
||||||
*/
|
*/
|
||||||
get unsignedSerialized(): string {
|
get unsignedSerialized(): string {
|
||||||
switch (this.inferType()) {
|
return this.#getSerialized(false, false);
|
||||||
case 0:
|
|
||||||
return _serializeLegacy(this);
|
|
||||||
case 1:
|
|
||||||
return _serializeEip2930(this);
|
|
||||||
case 2:
|
|
||||||
return _serializeEip1559(this);
|
|
||||||
case 3:
|
|
||||||
return _serializeEip4844(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(false, "unsupported transaction type", "UNSUPPORTED_OPERATION", { operation: ".unsignedSerialized" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -963,8 +1146,15 @@ export class Transaction implements TransactionLike<string> {
|
|||||||
if (tx.chainId != null) { result.chainId = tx.chainId; }
|
if (tx.chainId != null) { result.chainId = tx.chainId; }
|
||||||
if (tx.signature != null) { result.signature = Signature.from(tx.signature); }
|
if (tx.signature != null) { result.signature = Signature.from(tx.signature); }
|
||||||
if (tx.accessList != null) { result.accessList = tx.accessList; }
|
if (tx.accessList != null) { result.accessList = tx.accessList; }
|
||||||
|
|
||||||
|
// This will get overwritten by blobs, if present
|
||||||
if (tx.blobVersionedHashes != null) { result.blobVersionedHashes = tx.blobVersionedHashes; }
|
if (tx.blobVersionedHashes != null) { result.blobVersionedHashes = tx.blobVersionedHashes; }
|
||||||
|
|
||||||
|
// Make sure we assign the kzg before assigning blobs, which
|
||||||
|
// require the library in the event raw blob data is provided.
|
||||||
|
if (tx.kzg != null) { result.kzg = tx.kzg; }
|
||||||
|
if (tx.blobs != null) { result.blobs = tx.blobs; }
|
||||||
|
|
||||||
if (tx.hash != null) {
|
if (tx.hash != null) {
|
||||||
assertArgument(result.isSigned(), "unsigned transaction cannot define hash", "tx", tx);
|
assertArgument(result.isSigned(), "unsigned transaction cannot define hash", "tx", tx);
|
||||||
assertArgument(result.hash === tx.hash, "hash mismatch", "tx", tx);
|
assertArgument(result.hash === tx.hash, "hash mismatch", "tx", tx);
|
||||||
|
Loading…
Reference in New Issue
Block a user