UNPKG

ox

Version:

Ethereum Standard Library

843 lines (778 loc) 27.1 kB
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) })