UNPKG

ox

Version:

Ethereum Standard Library

988 lines (916 loc) 31.9 kB
import { Hex, Rlp, Secp256k1, TxEnvelopeLegacy, Value } from 'ox' import { assertType, describe, expect, expectTypeOf, test } from 'vp/test' import { accounts } from '../../../test/constants/accounts.js' import { anvilMainnet } from '../../../test/prool.js' describe('assert', () => { test('fee cap too high', () => { expect(() => TxEnvelopeLegacy.assert({ gasPrice: 2n ** 256n - 1n + 1n, chainId: 1, }), ).toThrowErrorMatchingInlineSnapshot( '[TransactionEnvelope.GasPriceTooHighError: The gas price (`gasPrice` = 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei) cannot be higher than the maximum allowed value (2^256-1).]', ) }) test('invalid chainId', () => { expect(() => TxEnvelopeLegacy.assert({ chainId: 0 }), ).toThrowErrorMatchingInlineSnapshot( `[TransactionEnvelope.InvalidChainIdError: Chain ID "0" is invalid.]`, ) }) test('invalid address', () => { expect(() => TxEnvelopeLegacy.assert({ to: '0x123', chainId: 1 })) .toThrowErrorMatchingInlineSnapshot(` [Address.InvalidAddressError: Address "0x123" is invalid. Details: Address is not a 20 byte (40 hexadecimal character) value.] `) }) }) describe('deserialize', () => { const transaction = TxEnvelopeLegacy.from({ gasPrice: Value.fromGwei('2'), to: accounts[1].address, nonce: 785n, value: Value.fromEther('1'), }) test('default', () => { const serialized = TxEnvelopeLegacy.serialize(transaction) const deserialized = TxEnvelopeLegacy.deserialize(serialized) assertType<TxEnvelopeLegacy.TxEnvelopeLegacy>(deserialized) expect(deserialized).toEqual(transaction) }) test('gas', () => { const transaction_gas = TxEnvelopeLegacy.from({ ...transaction, gas: 21001n, }) const serialized = TxEnvelopeLegacy.serialize(transaction_gas) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction_gas) }) test('data', () => { const transaction_data = TxEnvelopeLegacy.from({ ...transaction, data: '0x1234', }) const serialized = TxEnvelopeLegacy.serialize(transaction_data) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction_data) }) test('chainId', () => { const transaction_chainId = TxEnvelopeLegacy.from({ ...transaction, chainId: 69, }) const serialized = TxEnvelopeLegacy.serialize(transaction_chainId) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual( transaction_chainId, ) }) test('zeroish nonce', () => { const transaction_nonce = TxEnvelopeLegacy.from({ ...transaction, nonce: 0n, }) const serialized = TxEnvelopeLegacy.serialize(transaction_nonce) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction_nonce) }) test('signature', async () => { const signature = Secp256k1.sign({ payload: TxEnvelopeLegacy.getSignPayload(transaction), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeLegacy.serialize(transaction, { signature, }) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "gasPrice": 2000000000n, "nonce": 785n, "r": "0x6cb0e8d21e5baf998fb9a05f47acd83692dc148f90b81b332a152f020da0ae98", "s": "0x344e49bacb1ef7af7c2ffed9e88d3f0ae0aa4945c9da0a660a03717dd5621f98", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 28, "value": 1000000000000000000n, "yParity": 1, } `, ) }) test('signature', async () => { { const serialized = TxEnvelopeLegacy.serialize(transaction, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 0, }, }) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "gasPrice": 2000000000n, "nonce": 785n, "r": "0x0000000000000000000000000000000000000000000000000000000000000001", "s": "0x0000000000000000000000000000000000000000000000000000000000000000", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 27, "value": 1000000000000000000n, "yParity": 0, } `, ) } { const serialized = TxEnvelopeLegacy.serialize(transaction, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000001', yParity: 0, }, }) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "gasPrice": 2000000000n, "nonce": 785n, "r": "0x0000000000000000000000000000000000000000000000000000000000000000", "s": "0x0000000000000000000000000000000000000000000000000000000000000001", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 27, "value": 1000000000000000000n, "yParity": 0, } `, ) } }) test('signature + chainId', async () => { const transaction_chainId = TxEnvelopeLegacy.from({ ...transaction, chainId: 69, }) const signature = Secp256k1.sign({ payload: TxEnvelopeLegacy.getSignPayload(transaction_chainId), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeLegacy.serialize(transaction_chainId, { signature, }) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "chainId": 69, "gasPrice": 2000000000n, "nonce": 785n, "r": "0x2f43314322cf4c5dd645b028aa0b0dadff0fb73c41a6f0620ff1dfb11601ac30", "s": "0x66f37a65e139fa4b6df33a42ab5ccaeaa7a109382e7430caefd1deee63962626", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 173, "value": 1000000000000000000n, "yParity": 0, } `, ) }) describe('raw', () => { test('default', () => { const serialized = Rlp.fromHex([ Hex.fromNumber(0), // nonce Hex.fromNumber(1), // gasPrice Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data ]) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot(` { "gas": 1n, "gasPrice": 1n, "nonce": 0n, "to": "0x0000000000000000000000000000000000000000", "type": "legacy", "value": 0n, } `) }) test('empty sig', () => { const serialized = Rlp.fromHex([ Hex.fromNumber(0), // nonce Hex.fromNumber(1), // gasPrice Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x', // v '0x', // r '0x', // s ]) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot(` { "gas": 1n, "gasPrice": 1n, "nonce": 0n, "to": "0x0000000000000000000000000000000000000000", "type": "legacy", "value": 0n, } `) }) test('low sig coords', () => { const serialized = Rlp.fromHex([ Hex.fromNumber(0), // nonce Hex.fromNumber(1), // gasPrice Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x1b', // v Hex.fromNumber(69), // r Hex.fromNumber(420), // s ]) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot(` { "gas": 1n, "gasPrice": 1n, "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000045", "s": "0x00000000000000000000000000000000000000000000000000000000000001a4", "to": "0x0000000000000000000000000000000000000000", "type": "legacy", "v": 27, "value": 0n, "yParity": 0, } `) }) }) describe('errors', () => { test('invalid transaction (all missing)', () => { expect(() => TxEnvelopeLegacy.deserialize(Rlp.fromHex([]))) .toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "legacy" was provided. Serialized Transaction: "0xc0" Missing Attributes: nonce, gasPrice, gas, to, value, data] `) }) test('invalid transaction (some missing)', () => { expect(() => TxEnvelopeLegacy.deserialize(Rlp.fromHex(['0x00', '0x01']))) .toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "legacy" was provided. Serialized Transaction: "0xc20001" Missing Attributes: gas, to, value, data] `) }) test('invalid transaction (missing signature)', () => { expect(() => TxEnvelopeLegacy.deserialize( Rlp.fromHex(['0x', '0x', '0x', '0x', '0x', '0x', '0x']), ), ).toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "legacy" was provided. Serialized Transaction: "0xc780808080808080" Missing Attributes: r, s] `) }) test('invalid transaction (attribute overload)', () => { expect(() => TxEnvelopeLegacy.deserialize( Rlp.fromHex([ '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', '0x', ]), ), ).toThrowErrorMatchingInlineSnapshot(` [TransactionEnvelope.InvalidSerializedError: Invalid serialized transaction of type "legacy" was provided. Serialized Transaction: "0xca80808080808080808080"] `) }) test('invalid v', () => { expect(() => TxEnvelopeLegacy.deserialize( Rlp.fromHex([ Hex.fromNumber(0), // nonce Hex.fromNumber(1), // gasPrice Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data '0x', // v Hex.fromNumber(69), // r Hex.fromNumber(420), // s ]), ), ).toThrowErrorMatchingInlineSnapshot( '[Signature.InvalidVError: Value `0` is an invalid v value. v must be 27, 28 or >=35.]', ) expect(() => TxEnvelopeLegacy.deserialize( Rlp.fromHex([ Hex.fromNumber(0), // nonce Hex.fromNumber(1), // gasPrice Hex.fromNumber(1), // gas '0x0000000000000000000000000000000000000000', // to Hex.fromNumber(0), // value '0x', // data Hex.fromNumber(35), // v Hex.fromNumber(69), // r Hex.fromNumber(420), // s ]), ), ).toThrowErrorMatchingInlineSnapshot( '[Signature.InvalidVError: Value `35` is an invalid v value. v must be 27, 28 or >=35.]', ) }) }) }) describe('from', () => { test('default', () => { { const envelope = TxEnvelopeLegacy.from({}) expectTypeOf(envelope).toMatchTypeOf<TxEnvelopeLegacy.TxEnvelopeLegacy>() expect(envelope).toMatchInlineSnapshot(` { "type": "legacy", } `) } { const envelope = TxEnvelopeLegacy.from({ nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 69n, }) const serialized = TxEnvelopeLegacy.serialize(envelope) expect(TxEnvelopeLegacy.from(serialized)).toEqual(envelope) } { const envelope = TxEnvelopeLegacy.from({ chainId: 1, to: '0x0000000000000000000000000000000000000000', value: 69n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000001', v: 37, }) const serialized = TxEnvelopeLegacy.serialize(envelope) const envelope2 = TxEnvelopeLegacy.from(serialized) expect(envelope2).toEqual({ ...envelope, nonce: 0n, yParity: 0 }) } }) test('options: signature', () => { const envelope = TxEnvelopeLegacy.from( { nonce: 0n, to: '0x0000000000000000000000000000000000000000', value: 69n, type: 'legacy', }, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000001', yParity: 1, }, }, ) expect(envelope).toMatchInlineSnapshot(` { "nonce": 0n, "r": "0x0000000000000000000000000000000000000000000000000000000000000000", "s": "0x0000000000000000000000000000000000000000000000000000000000000001", "to": "0x0000000000000000000000000000000000000000", "type": "legacy", "v": 28, "value": 69n, "yParity": 1, } `) const serialized = TxEnvelopeLegacy.serialize(envelope) const envelope2 = TxEnvelopeLegacy.from(serialized) expect(envelope2).toEqual(envelope) const envelope3 = TxEnvelopeLegacy.from(serialized, { signature: { r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000000', yParity: 1, }, }) expect(envelope3).toEqual({ ...envelope, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000000', v: 28, }) }) }) describe('getSignPayload', () => { test('default', () => { const envelope = TxEnvelopeLegacy.from({ chainId: 1, gas: 21000n, gasPrice: 1000000000n, nonce: 665n, value: 1000000000000000000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', type: 'legacy', }) const hash = TxEnvelopeLegacy.hash(envelope, { presign: true }) expect(hash).toMatchInlineSnapshot( `"0x4c1dec0d90aa1a17cb0aa735b5550e43a1bd27cd1cacb8987522576fa6220e46"`, ) const signature = Secp256k1.sign({ payload: TxEnvelopeLegacy.getSignPayload(envelope), privateKey: accounts[0].privateKey, }) const envelope_signed = TxEnvelopeLegacy.from(envelope, { signature, }) { const hash = TxEnvelopeLegacy.hash(envelope_signed) expect(hash).toMatchInlineSnapshot( `"0x882958af32ef451898e54afcaa1c5bb56e5dff1bf47ac9d1b47751bd0b6c8ec8"`, ) } { const hash_presign = TxEnvelopeLegacy.getSignPayload(envelope_signed) expect(hash_presign).toEqual(hash) } }) }) describe('hash', () => { test('default', () => { const envelope = TxEnvelopeLegacy.from({ chainId: 1, gas: 21000n, gasPrice: 1000000000n, nonce: 665n, value: 1000000000000000000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', r: '0xacf664dcd984d082b68c434feb66ac684711babdeefe6f101bf8df88fc367a37', s: '0x5e0800058a9b5c2250bed60ee969a45b7445e562a8298c2d222d114e6dfbfcb9', v: 27, }) const hash = TxEnvelopeLegacy.hash(envelope) expect(hash).toMatchInlineSnapshot( `"0x5e427e088ae00b084b41e198c52440aa43b2d6f8f1f01246fd25d4e9b6ebfdab"`, ) }) }) describe('serialize', () => { const transaction = TxEnvelopeLegacy.from({ gasPrice: Value.fromGwei('2'), nonce: 785n, to: accounts[1].address, value: Value.fromEther('1'), }) test('default', () => { const serialized = TxEnvelopeLegacy.serialize(transaction) assertType<TxEnvelopeLegacy.Serialized>(serialized) expect(serialized).toEqual( '0xe88203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction) }) test('default (all zeros)', () => { const transaction = TxEnvelopeLegacy.from({ to: accounts[1].address, nonce: 0n, value: 0n, gas: 0n, gasPrice: 0n, }) const serialized = TxEnvelopeLegacy.serialize(transaction) expect(serialized).toEqual( '0xda8080809470997970c51812dc3a010c7d01b50e0d17dc79c88080', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "nonce": 0n, "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", } `, ) }) test('minimal', () => { const transaction = TxEnvelopeLegacy.from({}) const serialized = TxEnvelopeLegacy.serialize(transaction) expect(serialized).toEqual('0xc6808080808080') expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "nonce": 0n, "type": "legacy", } `, ) }) test('minimal (w/ gasPrice)', () => { const transaction = TxEnvelopeLegacy.from({ nonce: 0n, gasPrice: Value.fromGwei('2'), }) const serialized = TxEnvelopeLegacy.serialize(transaction) expect(serialized).toEqual('0xca80847735940080808080') expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction) }) test('gas', () => { const transaction_gas = TxEnvelopeLegacy.from({ ...transaction, gas: 21001n, }) const serialized = TxEnvelopeLegacy.serialize(transaction_gas) expect(serialized).toEqual( '0xea82031184773594008252099470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction_gas) }) test('data', () => { const transaction_gas = TxEnvelopeLegacy.from({ ...transaction, data: '0x1234', }) const serialized = TxEnvelopeLegacy.serialize(transaction_gas) expect(serialized).toEqual( '0xea8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000821234', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual(transaction_gas) }) test('chainId', () => { const transaction_chainId = TxEnvelopeLegacy.from({ ...transaction, chainId: 69, }) const serialized = TxEnvelopeLegacy.serialize(transaction_chainId) expect(serialized).toEqual( '0xeb8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080458080', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toEqual( transaction_chainId, ) }) test('options: signature', async () => { const signature = Secp256k1.sign({ payload: TxEnvelopeLegacy.getSignPayload(transaction), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeLegacy.serialize(transaction, { signature, }) expect(serialized).toEqual( '0xf86b8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000801ca06cb0e8d21e5baf998fb9a05f47acd83692dc148f90b81b332a152f020da0ae98a0344e49bacb1ef7af7c2ffed9e88d3f0ae0aa4945c9da0a660a03717dd5621f98', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "gasPrice": 2000000000n, "nonce": 785n, "r": "0x6cb0e8d21e5baf998fb9a05f47acd83692dc148f90b81b332a152f020da0ae98", "s": "0x344e49bacb1ef7af7c2ffed9e88d3f0ae0aa4945c9da0a660a03717dd5621f98", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 28, "value": 1000000000000000000n, "yParity": 1, } `, ) }) test('options: signature', () => { expect( TxEnvelopeLegacy.serialize(transaction, { signature: { r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', yParity: 1, }, }), ).toEqual( '0xf86b8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000801ca060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) expect( TxEnvelopeLegacy.serialize(transaction, { signature: { r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', yParity: 0, }, }), ).toEqual( '0xf86b8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a7640000801ba060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', ) }) test('behavior: signature + chainId', async () => { const transaction_chainId = TxEnvelopeLegacy.from({ ...transaction, chainId: 69, }) const signature = Secp256k1.sign({ payload: TxEnvelopeLegacy.getSignPayload(transaction_chainId), privateKey: accounts[0].privateKey, }) const serialized = TxEnvelopeLegacy.serialize(transaction_chainId, { signature, }) expect(serialized).toEqual( '0xf86c8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a76400008081ada02f43314322cf4c5dd645b028aa0b0dadff0fb73c41a6f0620ff1dfb11601ac30a066f37a65e139fa4b6df33a42ab5ccaeaa7a109382e7430caefd1deee63962626', ) expect(TxEnvelopeLegacy.deserialize(serialized)).toMatchInlineSnapshot( ` { "chainId": 69, "gasPrice": 2000000000n, "nonce": 785n, "r": "0x2f43314322cf4c5dd645b028aa0b0dadff0fb73c41a6f0620ff1dfb11601ac30", "s": "0x66f37a65e139fa4b6df33a42ab5ccaeaa7a109382e7430caefd1deee63962626", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "legacy", "v": 173, "value": 1000000000000000000n, "yParity": 0, } `, ) }) test('behavior: inferred chainId', () => { { const transaction = TxEnvelopeLegacy.from({ gas: 51627n, gasPrice: 3000000000n, hash: '0x3eaa88a766e82cbe53c95218ab4c3cf316325802b5f75d086b5121007b918e92', nonce: 117n, to: '0x55d398326f99059ff775485246999027b3197955', value: 0n, v: 84475, r: '0x73b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bf', s: '0x354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23', }) expect(TxEnvelopeLegacy.serialize(transaction)).toMatchInlineSnapshot( '"0xf8667584b2d05e0082c9ab9455d398326f99059ff775485246999027b31979558080830149fba073b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bfa0354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23"', ) } { const transaction = TxEnvelopeLegacy.from({ gas: 51627n, gasPrice: 3000000000n, hash: '0x3eaa88a766e82cbe53c95218ab4c3cf316325802b5f75d086b5121007b918e92', nonce: 117n, to: '0x55d398326f99059ff775485246999027b3197955', value: 0n, v: 84476, r: '0x73b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bf', s: '0x354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23', }) expect(TxEnvelopeLegacy.serialize(transaction)).toMatchInlineSnapshot( '"0xf8667584b2d05e0082c9ab9455d398326f99059ff775485246999027b31979558080830149fca073b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bfa0354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23"', ) } { const transaction = TxEnvelopeLegacy.from({ gas: 51627n, gasPrice: 3000000000n, hash: '0x3eaa88a766e82cbe53c95218ab4c3cf316325802b5f75d086b5121007b918e92', nonce: 117n, to: '0x55d398326f99059ff775485246999027b3197955', transactionIndex: null, value: 0n, type: 'legacy', v: 35, r: '0x73b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bf', s: '0x354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23', }) expect(TxEnvelopeLegacy.serialize(transaction)).toMatchInlineSnapshot( '"0xf8637584b2d05e0082c9ab9455d398326f99059ff775485246999027b319795580801ba073b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bfa0354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23"', ) } { const transaction = TxEnvelopeLegacy.from({ gas: 51627n, gasPrice: 3000000000n, hash: '0x3eaa88a766e82cbe53c95218ab4c3cf316325802b5f75d086b5121007b918e92', nonce: 117n, to: '0x55d398326f99059ff775485246999027b3197955', transactionIndex: null, value: 0n, type: 'legacy', v: 36, r: '0x73b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bf', s: '0x354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23', }) expect(TxEnvelopeLegacy.serialize(transaction)).toMatchInlineSnapshot( '"0xf8637584b2d05e0082c9ab9455d398326f99059ff775485246999027b319795580801ca073b39769ff4a36515c8fca546550a3fdafebbf37fa9e22be2d92b44653ade7bfa0354c756a1aa3346e9b3ea5423ac99acfc005e9cce2cd698e14d792f43fa15a23"', ) } }) test('behavior: zeroish sig coords', () => { const serialized = TxEnvelopeLegacy.serialize({ chainId: 17000, gas: BigInt('0x52080'), gasPrice: 0n, nonce: 0n, r: '0x0000000000000000000000000000000000000000000000000000000000000000', s: '0x0000000000000000000000000000000000000000000000000000000000000000', v: 0, to: '0xc000000000000000000000000000000000000000', type: 'legacy', value: BigInt('0x0'), }) expect(serialized).toEqual( '0xe280808305208094c00000000000000000000000000000000000000080808284d88080', ) }) test('error: invalid v', () => { expect(() => TxEnvelopeLegacy.serialize({ ...transaction, r: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', s: '0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe', v: 29, }), ).toThrowErrorMatchingInlineSnapshot( '[Signature.InvalidVError: Value `29` is an invalid v value. v must be 27, 28 or >=35.]', ) }) }) describe('toRpc', () => { test('default', () => { const transaction = TxEnvelopeLegacy.toRpc({ chainId: 1, nonce: 0n, gas: 21000n, gasPrice: Value.fromGwei('10'), to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) expect(transaction).toMatchInlineSnapshot(` { "chainId": "0x1", "data": undefined, "gas": "0x5208", "gasPrice": "0x2540be400", "nonce": "0x0", "r": "0x0000000000000000000000000000000000000000000000000000000000000001", "s": "0x0000000000000000000000000000000000000000000000000000000000000002", "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "0x0", "v": "0x25", "value": "0xde0b6b3a7640000", "yParity": "0x0", } `) }) test('numberish inputs', () => { const fromBigint = TxEnvelopeLegacy.toRpc({ chainId: 1, nonce: 0n, gas: 21000n, gasPrice: 1000n, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000n, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) const fromNumber = TxEnvelopeLegacy.toRpc({ chainId: 1, nonce: 0, gas: 21000, gasPrice: 1000, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000, r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) const fromHex = TxEnvelopeLegacy.toRpc({ chainId: 1, nonce: '0x0', gas: '0x5208', gasPrice: '0x3e8', to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: '0x3e8', r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 0, }) expect(fromBigint).toEqual(fromNumber) expect(fromBigint).toEqual(fromHex) }) test('behavior: nullish', () => { const transaction = TxEnvelopeLegacy.toRpc({ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', }) expect(transaction).toMatchInlineSnapshot(` { "chainId": undefined, "data": undefined, "to": "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", "type": "0x0", } `) }) test('behavior: preserves explicit `v`', () => { const transaction = TxEnvelopeLegacy.toRpc({ chainId: 1, to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', v: 38, yParity: 1, }) expect(transaction.v).toBe('0x26') }) test('behavior: pre-EIP-155 (no chainId)', () => { const transaction = TxEnvelopeLegacy.toRpc({ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', r: '0x0000000000000000000000000000000000000000000000000000000000000001', s: '0x0000000000000000000000000000000000000000000000000000000000000002', yParity: 1, }) expect(transaction.v).toBe('0x1c') }) test('behavior: network', async () => { const nonce = await anvilMainnet.request({ method: 'eth_getTransactionCount', params: [accounts[0].address, 'pending'], }) const transaction = TxEnvelopeLegacy.from({ chainId: 1, nonce: BigInt(nonce), gas: 21000n, gasPrice: Value.fromGwei('10'), from: accounts[0].address, to: accounts[1].address, value: Value.fromEther('1'), }) const rpc = TxEnvelopeLegacy.toRpc(transaction) const hash = await anvilMainnet.request({ method: 'eth_sendTransaction', params: [rpc], }) expect(hash).toBeDefined() const tx = await anvilMainnet.request({ method: 'eth_getTransactionByHash', params: [hash], }) expect(tx).toMatchObject({ chainId: '0x1', from: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', gas: '0x5208', gasPrice: '0x2540be400', input: '0x', to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', type: '0x0', value: '0xde0b6b3a7640000', }) }) }) describe('validate', () => { test('default', () => { expect( TxEnvelopeLegacy.validate({ chainId: 1, gasPrice: 69n, }), ).toBe(true) expect( TxEnvelopeLegacy.validate({ chainId: 1, gasPrice: 2n ** 257n, }), ).toBe(false) }) }) test('exports', () => { expect(Object.keys(TxEnvelopeLegacy)).toMatchInlineSnapshot(` [ "type", "assert", "deserialize", "from", "getSignPayload", "hash", "serialize", "toRpc", "validate", ] `) })