ox
Version:
Ethereum Standard Library
843 lines (778 loc) • 27.1 kB
text/typescript
import {
Address,
Blobs,
Hex,
Rlp,
RpcTransport,
Secp256k1,
TxEnvelopeEip4844,
Value,
} from 'ox'
import { assertType, describe, expect, test } from 'vp/test'
import { accounts } from '../../../test/constants/accounts.js'
import { kzg } from '../../../test/kzg.js'
describe('assert', () => {
test('empty blobs', () => {
expect(() =>
TxEnvelopeEip4844.assert({
blobVersionedHashes: [],
chainId: 1,
}),
).toThrowErrorMatchingInlineSnapshot(
'[Blobs.EmptyBlobVersionedHashesError: Blob versioned hashes must not be empty.]',
)
})
test('invalid blob length', () => {
expect(() =>
TxEnvelopeEip4844.assert({
blobVersionedHashes: ['0xcafebabe'],
chainId: 1,
}),
).toThrowErrorMatchingInlineSnapshot(`
[Blobs.InvalidVersionedHashSizeError: Versioned hash "0xcafebabe" size is invalid.
Expected: 32
Received: 4]
`)
})
test('invalid blob version', () => {
expect(() =>
TxEnvelopeEip4844.assert({
blobVersionedHashes: [
'0xcafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
chainId: 1,
}),
).toThrowErrorMatchingInlineSnapshot(`
[Blobs.InvalidVersionedHashVersionError: Versioned hash "0xcafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" version is invalid.
Expected: 1
Received: 202]
`)
})
test('fee cap too high', () => {
expect(() =>
TxEnvelopeEip4844.assert({
blobVersionedHashes: [
'0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
maxFeePerGas: 2n ** 256n - 1n + 1n,
chainId: 1,
}),
).toThrowErrorMatchingInlineSnapshot(
'[TransactionEnvelope.FeeCapTooHighError: The fee cap (`maxFeePerGas`/`maxPriorityFeePerGas` = 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei) cannot be higher than the maximum allowed value (2^256-1).]',
)
})
})
describe('deserialize', () => {
const blobs = Blobs.from(Hex.fromString('abcd'))
const blobVersionedHashes = Blobs.toVersionedHashes(blobs, { kzg })
const transaction = TxEnvelopeEip4844.from({
to: accounts[1].address,
nonce: 785n,
value: Value.fromEther('1'),
chainId: 1,
maxFeePerGas: Value.fromGwei('20'),
maxPriorityFeePerGas: Value.fromGwei('10'),
maxFeePerBlobGas: Value.fromGwei('2'),
blobVersionedHashes,
})
test('default', () => {
const serialized = TxEnvelopeEip4844.serialize(transaction)
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
assertType<TxEnvelopeEip4844.TxEnvelopeEip4844>(deserialized)
expect(deserialized).toEqual(transaction)
})
test('minimal', () => {
const transaction = TxEnvelopeEip4844.from({
chainId: 1,
blobVersionedHashes: [
'0x01adbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
],
nonce: 0n,
})
const serialized = TxEnvelopeEip4844.serialize(transaction)
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
expect(deserialized).toEqual(transaction)
})
test('gas', () => {
const transaction_gas = TxEnvelopeEip4844.from({
...transaction,
gas: 21001n,
})
const serialized = TxEnvelopeEip4844.serialize(transaction_gas)
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
assertType<TxEnvelopeEip4844.TxEnvelopeEip4844>(deserialized)
expect(deserialized).toEqual(transaction_gas)
})
test('accessList', () => {
const transaction_accessList = TxEnvelopeEip4844.from({
...transaction,
accessList: [
{
address: '0x0000000000000000000000000000000000000000',
storageKeys: [
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
],
},
],
})
const serialized = TxEnvelopeEip4844.serialize(transaction_accessList)
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
assertType<TxEnvelopeEip4844.TxEnvelopeEip4844>(deserialized)
expect(deserialized).toEqual(transaction_accessList)
})
test('data', () => {
const transaction_data = TxEnvelopeEip4844.from({
...transaction,
data: '0xdeadbeef',
})
const serialized = TxEnvelopeEip4844.serialize(transaction_data)
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
assertType<TxEnvelopeEip4844.TxEnvelopeEip4844>(deserialized)
expect(deserialized).toEqual(transaction_data)
})
test('signature', async () => {
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(transaction),
privateKey: accounts[0].privateKey,
})
const serialized = TxEnvelopeEip4844.serialize(transaction, {
signature,
})
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual({
...transaction,
...signature,
})
})
test('sidecars', () => {
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(transaction),
privateKey: accounts[0].privateKey,
})
const sidecars = {
blobs,
commitments: Blobs.toCommitments(blobs, { kzg }),
cellProofs: Blobs.toCellProofs(blobs, { kzg }),
}
const serialized = TxEnvelopeEip4844.serialize(transaction, {
signature,
sidecars,
})
const deserialized = TxEnvelopeEip4844.deserialize(serialized)
expect(deserialized).toEqual({
...transaction,
...signature,
sidecars,
})
expect(deserialized.sidecars?.cellProofs.length).toBe(128 * blobs.length)
})
test('errors: legacy 4-element wrapper rejected', () => {
const txBody: readonly (Hex.Hex | Hex.Hex[])[] = [
Hex.fromNumber(1),
Hex.fromNumber(0),
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
[] as Hex.Hex[],
'0x',
['0x0100000000000000000000000000000000000000000000000000000000000000'],
]
expect(() =>
TxEnvelopeEip4844.deserialize(
`0x03${Rlp.fromHex([txBody, [], [], []] as never).slice(2)}`,
),
).toThrowError(TxEnvelopeEip4844.LegacyBlobSidecarWrapperError)
})
describe('errors', () => {
test('invalid access list (invalid address)', () => {
expect(() =>
TxEnvelopeEip4844.deserialize(
`0x03${Rlp.fromHex([
Hex.fromNumber(1), // chainId
Hex.fromNumber(0), // nonce
Hex.fromNumber(1), // maxPriorityFeePerGas
Hex.fromNumber(1), // maxFeePerGas
Hex.fromNumber(1), // gas
'0x0000000000000000000000000000000000000000', // to
Hex.fromNumber(0), // value
'0x', // data
[
[
'0x',
[
'0x0000000000000000000000000000000000000000000000000000000000000001',
],
],
], // accessList
'0x', // maxFeePerBlobGas,
['0x'], // blobVersionedHashes
]).slice(2)}`,
),
).toThrowErrorMatchingInlineSnapshot(`
[Address.InvalidAddressError: Address "0x" is invalid.
Details: Address is not a 20 byte (40 hexadecimal character) value.]
`)
expect(() =>
TxEnvelopeEip4844.deserialize(
`0x03${Rlp.fromHex([
Hex.fromNumber(1), // chainId
Hex.fromNumber(0), // nonce
Hex.fromNumber(1), // maxPriorityFeePerGas
Hex.fromNumber(1), // maxFeePerGas
Hex.fromNumber(1), // gas
'0x0000000000000000000000000000000000000000', // to
Hex.fromNumber(0), // value
'0x', // data
[['0x123456', ['0x0']]], // accessList
'0x', // maxFeePerBlobGas,
['0x'], // blobVersionedHashes
]).slice(2)}`,
),
).toThrowErrorMatchingInlineSnapshot(`
[Address.InvalidAddressError: Address "0x123456" is invalid.
Details: Address is not a 20 byte (40 hexadecimal character) value.]
`)
})
test('invalid transaction (all missing)', () => {
expect(() =>
TxEnvelopeEip4844.deserialize(`0x03${Rlp.fromHex([]).slice(2)}`),
).toThrowErrorMatchingInlineSnapshot(`
[TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip4844" was provided.
Serialized Transaction: "0x03c0"
Missing Attributes: chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, maxFeePerBlobGas, blobVersionedHashes]
`)
})
test('invalid transaction (some missing)', () => {
expect(() =>
TxEnvelopeEip4844.deserialize(
`0x03${Rlp.fromHex(['0x00', '0x01']).slice(2)}`,
),
).toThrowErrorMatchingInlineSnapshot(`
[TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip4844" was provided.
Serialized Transaction: "0x03c20001"
Missing Attributes: maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, maxFeePerBlobGas, blobVersionedHashes]
`)
})
test('invalid transaction (missing signature)', () => {
expect(() =>
TxEnvelopeEip4844.deserialize(
`0x03${Rlp.fromHex([
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
'0x',
]).slice(2)}`,
),
).toThrowErrorMatchingInlineSnapshot(`
[TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "eip4844" was provided.
Serialized Transaction: "0x03cc808080808080808080808080"
Missing Attributes: r, s]
`)
})
})
})
describe('from', () => {
test('default', () => {
{
const envelope = TxEnvelopeEip4844.from({
blobVersionedHashes: [
'0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
chainId: 1,
nonce: 0n,
})
expect(envelope).toMatchInlineSnapshot(`
{
"blobVersionedHashes": [
"0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
],
"chainId": 1,
"nonce": 0n,
"type": "eip4844",
}
`)
const serialized = TxEnvelopeEip4844.serialize(envelope)
const envelope2 = TxEnvelopeEip4844.from(serialized)
expect(envelope2).toEqual(envelope)
}
{
const envelope = TxEnvelopeEip4844.from({
blobVersionedHashes: [
'0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
chainId: 1,
nonce: 0n,
r: '0x0000000000000000000000000000000000000000000000000000000000000000',
s: '0x0000000000000000000000000000000000000000000000000000000000000001',
yParity: 0,
})
expect(envelope).toMatchInlineSnapshot(`
{
"blobVersionedHashes": [
"0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
],
"chainId": 1,
"nonce": 0n,
"r": "0x0000000000000000000000000000000000000000000000000000000000000000",
"s": "0x0000000000000000000000000000000000000000000000000000000000000001",
"type": "eip4844",
"yParity": 0,
}
`)
const serialized = TxEnvelopeEip4844.serialize(envelope)
const envelope2 = TxEnvelopeEip4844.from(serialized)
expect(envelope2).toEqual(envelope)
}
})
test('options: signature', () => {
const envelope = TxEnvelopeEip4844.from(
{
blobVersionedHashes: [
'0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
chainId: 1,
nonce: 0n,
},
{
signature: {
r: '0x0000000000000000000000000000000000000000000000000000000000000000',
s: '0x0000000000000000000000000000000000000000000000000000000000000001',
yParity: 0,
},
},
)
expect(envelope).toMatchInlineSnapshot(`
{
"blobVersionedHashes": [
"0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe",
],
"chainId": 1,
"nonce": 0n,
"r": "0x0000000000000000000000000000000000000000000000000000000000000000",
"s": "0x0000000000000000000000000000000000000000000000000000000000000001",
"type": "eip4844",
"yParity": 0,
}
`)
const serialized = TxEnvelopeEip4844.serialize(envelope)
const envelope2 = TxEnvelopeEip4844.from(serialized)
expect(envelope2).toEqual(envelope)
})
})
describe('from', () => {
test('default', () => {
const envelope = TxEnvelopeEip4844.from({
blobVersionedHashes: [
'0x01febabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe',
],
chainId: 1,
gas: 21000n,
maxFeePerGas: 13000000000n,
nonce: 665n,
value: 1000000000000000000n,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
type: 'eip4844',
})
const hash = TxEnvelopeEip4844.hash(envelope, { presign: true })
expect(hash).toMatchInlineSnapshot(
`"0xd5c811a922a14455151761e77bcc84bf590bb8dbf2d9a79d4c890f561a6dcd39"`,
)
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(envelope),
privateKey: accounts[0].privateKey,
})
const envelope_signed = TxEnvelopeEip4844.from(envelope, {
signature,
})
{
const hash = TxEnvelopeEip4844.hash(envelope_signed)
expect(hash).toMatchInlineSnapshot(
`"0xfae853c3fefc9481eb674943ddb34dca24c2959f26d2ce6917d45c8faad684a8"`,
)
}
{
const hash_presign = TxEnvelopeEip4844.getSignPayload(envelope_signed)
expect(hash_presign).toEqual(hash)
}
})
})
describe('hash', () => {
test('default', () => {
const envelope = TxEnvelopeEip4844.from({
blobVersionedHashes: [
'0x0100000000000000000000000000000000000000000000000000000000000000',
],
chainId: 1,
gas: 21000n,
maxFeePerGas: 1000000000n,
maxPriorityFeePerGas: 1000000000n,
nonce: 665n,
value: 1000000000000000000n,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
type: 'eip4844',
r: '0xacf664dcd984d082b68c434feb66ac684711babdeefe6f101bf8df88fc367a37',
s: '0x5e0800058a9b5c2250bed60ee969a45b7445e562a8298c2d222d114e6dfbfcb9',
yParity: 0,
})
const hash = TxEnvelopeEip4844.hash(envelope)
expect(hash).toMatchInlineSnapshot(
`"0xf3920f47c878feb9c81f159612c8a324fbd7dbf82ec937bf348e3d42e712a03d"`,
)
})
})
describe('serialize', () => {
const blobs = Blobs.from(Hex.fromString('abcd'))
const blobVersionedHashes = Blobs.toVersionedHashes(blobs, { kzg })
const transaction = TxEnvelopeEip4844.from({
chainId: 1,
nonce: 785n,
to: accounts[1].address,
value: Value.fromEther('1'),
blobVersionedHashes,
})
test('default', () => {
const serialized = TxEnvelopeEip4844.serialize(transaction)
assertType<TxEnvelopeEip4844.Serialized>(serialized)
expect(serialized).toEqual(
'0x03f84a018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c080e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed80261',
)
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual(transaction)
})
test('minimal', () => {
const transaction = TxEnvelopeEip4844.from({
blobVersionedHashes,
chainId: 1,
nonce: 0n,
})
const serialized = TxEnvelopeEip4844.serialize(transaction)
expect(serialized).toMatchInlineSnapshot(
`"0x03ec0180808080808080c080e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed80261"`,
)
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual(transaction)
})
test('fees', () => {
const transaction = TxEnvelopeEip4844.from({
chainId: 1,
blobVersionedHashes,
maxFeePerBlobGas: Value.fromGwei('20'),
maxFeePerGas: Value.fromGwei('20'),
maxPriorityFeePerGas: Value.fromGwei('1'),
nonce: 0n,
})
const serialized = TxEnvelopeEip4844.serialize(transaction)
expect(serialized).toMatchInlineSnapshot(
`"0x03f83a0180843b9aca008504a817c80080808080c08504a817c800e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed80261"`,
)
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual(transaction)
})
test('fees', () => {
const transaction = TxEnvelopeEip4844.from({
chainId: 1,
blobVersionedHashes,
gas: 69420n,
nonce: 0n,
})
const serialized = TxEnvelopeEip4844.serialize(transaction)
expect(serialized).toMatchInlineSnapshot(
`"0x03ef0180808083010f2c808080c080e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed80261"`,
)
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual(transaction)
})
test('no blobVersionedHashes', () => {
expect(() =>
// @ts-expect-error
TxEnvelopeEip4844.from({
chainId: 1,
nonce: 0n,
}),
).toThrowErrorMatchingInlineSnapshot(
`[Blobs.EmptyBlobVersionedHashesError: Blob versioned hashes must not be empty.]`,
)
})
test('options: signature', async () => {
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(transaction),
privateKey: accounts[0].privateKey,
})
const serialized = TxEnvelopeEip4844.serialize(transaction, {
signature,
})
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual({
...transaction,
...signature,
})
})
test('options: sidecars', () => {
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(transaction),
privateKey: accounts[0].privateKey,
})
const sidecars = {
blobs,
commitments: Blobs.toCommitments(blobs, { kzg }),
cellProofs: Blobs.toCellProofs(blobs, { kzg }),
}
const serialized = TxEnvelopeEip4844.serialize(transaction, {
signature,
sidecars,
})
// Wrapper RLP must decode into exactly 5 elements: tx_body,
// wrapper_version=0x01, blobs, commitments, cell_proofs.
const wrapper = Rlp.toHex(Hex.slice(serialized, 1))
expect(Array.isArray(wrapper)).toBe(true)
expect(wrapper.length).toBe(5)
expect(wrapper[1]).toBe('0x01')
const wrapperBlobs = wrapper[2] as readonly Hex.Hex[]
const wrapperCommitments = wrapper[3] as readonly Hex.Hex[]
const wrapperCellProofs = wrapper[4] as readonly Hex.Hex[]
expect(wrapperBlobs.length).toBe(blobs.length)
expect(wrapperCommitments.length).toBe(blobs.length)
expect(wrapperCellProofs.length).toBe(128 * blobs.length)
expect(TxEnvelopeEip4844.deserialize(serialized)).toEqual({
...transaction,
...signature,
sidecars,
})
// Hash must ignore sidecars (must equal hash of bare envelope).
const signed = TxEnvelopeEip4844.from(transaction, { signature })
const signedWithSidecars = TxEnvelopeEip4844.from(transaction, {
signature,
})
;(signedWithSidecars as { sidecars?: typeof sidecars }).sidecars = sidecars
expect(TxEnvelopeEip4844.hash(signedWithSidecars)).toBe(
TxEnvelopeEip4844.hash(signed),
)
})
test('options: signature', () => {
expect(
TxEnvelopeEip4844.serialize(transaction, {
signature: {
r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
yParity: 1,
},
}),
).toEqual(
'0x03f88d018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c080e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed8026101a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
)
expect(
TxEnvelopeEip4844.serialize(
transaction,
{
signature: {
r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
yParity: 0,
},
},
),
).toEqual(
'0x03f88d018203118080809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c080e1a001627c687261b0e7f8638af1112efa8a77e23656f6e7945275b19e9deed8026180a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe',
)
})
})
describe('toRpc', () => {
const blobs = Blobs.from('0xdeadbeef')
const blobVersionedHashes = Blobs.toVersionedHashes(blobs, { kzg })
test('default', () => {
const transaction = TxEnvelopeEip4844.toRpc({
blobVersionedHashes,
chainId: 1,
nonce: 0n,
maxFeePerGas: 1000000000n,
gas: 21000n,
maxPriorityFeePerGas: 100000000n,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: 1000000000000000000n,
r: '0x0000000000000000000000000000000000000000000000000000000000000001',
s: '0x0000000000000000000000000000000000000000000000000000000000000002',
yParity: 0,
})
expect(transaction).toMatchInlineSnapshot(`
{
"blobVersionedHashes": [
"0x01a24709d3997e8b217fe5460aef10ee515513ceba0362bf2d02a3ba73d7cb09",
],
"chainId": "0x1",
"data": undefined,
"gas": "0x5208",
"maxFeePerGas": "0x3b9aca00",
"maxPriorityFeePerGas": "0x5f5e100",
"nonce": "0x0",
"r": "0x0000000000000000000000000000000000000000000000000000000000000001",
"s": "0x0000000000000000000000000000000000000000000000000000000000000002",
"to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
"type": "0x3",
"value": "0xde0b6b3a7640000",
"yParity": "0x0",
}
`)
})
test('numberish inputs', () => {
const fromBigint = TxEnvelopeEip4844.toRpc({
blobVersionedHashes,
chainId: 1,
nonce: 0n,
maxFeePerGas: 1000n,
gas: 21000n,
maxPriorityFeePerGas: 100n,
maxFeePerBlobGas: 1000n,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: 1000n,
r: '0x0000000000000000000000000000000000000000000000000000000000000001',
s: '0x0000000000000000000000000000000000000000000000000000000000000002',
yParity: 0,
})
const fromNumber = TxEnvelopeEip4844.toRpc({
blobVersionedHashes,
chainId: 1,
nonce: 0,
maxFeePerGas: 1000,
gas: 21000,
maxPriorityFeePerGas: 100,
maxFeePerBlobGas: 1000,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: 1000,
r: '0x0000000000000000000000000000000000000000000000000000000000000001',
s: '0x0000000000000000000000000000000000000000000000000000000000000002',
yParity: 0,
})
const fromHex = TxEnvelopeEip4844.toRpc({
blobVersionedHashes,
chainId: '0x1',
nonce: '0x0',
maxFeePerGas: '0x3e8',
gas: '0x5208',
maxPriorityFeePerGas: '0x64',
maxFeePerBlobGas: '0x3e8',
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
value: '0x3e8',
r: '0x0000000000000000000000000000000000000000000000000000000000000001',
s: '0x0000000000000000000000000000000000000000000000000000000000000002',
yParity: '0x0',
})
expect(fromBigint).toEqual(fromNumber)
expect(fromBigint).toEqual(fromHex)
})
test('behavior: nullish', () => {
const transaction = TxEnvelopeEip4844.toRpc({
blobVersionedHashes,
chainId: 1,
to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
})
expect(transaction).toMatchInlineSnapshot(`
{
"blobVersionedHashes": [
"0x01a24709d3997e8b217fe5460aef10ee515513ceba0362bf2d02a3ba73d7cb09",
],
"chainId": "0x1",
"data": undefined,
"to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8",
"type": "0x3",
}
`)
})
})
describe('validate', () => {
test('default', () => {
expect(
TxEnvelopeEip4844.validate({
blobVersionedHashes: [
'0x0100000000000000000000000000000000000000000000000000000000000000',
],
chainId: 1,
}),
).toBe(true)
expect(
TxEnvelopeEip4844.validate({
blobVersionedHashes: [],
chainId: 1,
}),
).toBe(false)
})
})
test('exports', () => {
expect(Object.keys(TxEnvelopeEip4844)).toMatchInlineSnapshot(`
[
"wrapperVersion",
"serializedType",
"type",
"assert",
"deserialize",
"from",
"getSignPayload",
"hash",
"serialize",
"toRpc",
"validate",
"LegacyBlobSidecarWrapperError",
]
`)
})
describe.skip('sepolia blob (PeerDAS network wrapper)', () => {
const rpcUrl = process.env.VITE_SEPOLIA_RPC_URL ?? ''
const privateKey = (process.env.VITE_SEPOLIA_PRIVATE_KEY ??
'0x') as `0x${string}`
test('send blob transaction via eth_sendRawTransaction', async () => {
const transport = RpcTransport.fromHttp(rpcUrl)
// 1. Build a small blob from a string payload.
const payload = Hex.fromString(
`ox PeerDAS sepolia e2e ${new Date().toISOString()}`,
)
const blobs = Blobs.from(payload)
// 2. Compute commitments, versioned hashes, and cell proofs.
const commitments = Blobs.toCommitments(blobs, { kzg })
const blobVersionedHashes = Blobs.commitmentsToVersionedHashes(commitments)
const cellProofs = Blobs.toCellProofs(blobs, { kzg })
const address = Address.fromPublicKey(
Secp256k1.getPublicKey({ privateKey }),
)
// 3. Pull nonce from RPC.
const nonceHex = await transport.request({
method: 'eth_getTransactionCount',
params: [address, 'pending'],
})
const nonce = BigInt(nonceHex)
// 4. Build the unsigned envelope.
const envelope = TxEnvelopeEip4844.from({
chainId: 11155111,
nonce,
to: address,
value: 0n,
gas: 21000n,
maxFeePerGas: Value.fromGwei('50'),
maxPriorityFeePerGas: Value.fromGwei('1'),
maxFeePerBlobGas: Value.fromGwei('10'),
blobVersionedHashes,
})
// 5. Sign and serialize WITH sidecars (5-element wrapper).
const signature = Secp256k1.sign({
payload: TxEnvelopeEip4844.getSignPayload(envelope),
privateKey,
})
const serialized = TxEnvelopeEip4844.serialize(envelope, {
signature,
sidecars: { blobs, commitments, cellProofs },
})
// 6. Broadcast and wait for inclusion.
const receipt = await transport.request({
method: 'eth_sendRawTransactionSync',
params: [serialized],
})
// 7. Assert success.
expect(receipt.status).toBe('0x1')
expect(receipt.blobGasUsed).toBeDefined()
expect(BigInt(receipt.blobGasUsed!)).toBeGreaterThan(0n)
}, 180_000)
})