diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts index b00ebddb93..870ff5b1e7 100644 --- a/packages/relay/src/formatters.ts +++ b/packages/relay/src/formatters.ts @@ -147,7 +147,6 @@ const formatContractResult = (cr: any) => { const commonFields = { blockHash: toHash32(cr.block_hash), blockNumber: nullableNumberTo0x(cr.block_number), - chainId: cr.chain_id, from: cr.from.substring(0, 42), gas: nanOrNumberTo0x(cr.gas_used), gasPrice: toNullIfEmptyHex(cr.gas_price), @@ -161,6 +160,10 @@ const formatContractResult = (cr: any) => { type: nullableNumberTo0x(cr.type), v: cr.type === null ? null : nanOrNumberTo0x(cr.v), value: nanOrNumberTo0x(cr.amount), + // for legacy EIP155 with tx.chainId=0x0, mirror-node will return a '0x' (EMPTY_HEX) value for contract result's chain_id + // which is incompatibile with certain tools (i.e. foundry). By setting this field, chainId, to undefined, the end jsonrpc + // object will leave out this field, which is the proper behavior for other tools to be compatible with. + chainId: cr.chain_id === EMPTY_HEX ? undefined : cr.chain_id, }; switch (cr.type) { diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts index 78b190bf02..ef61d5c63a 100644 --- a/packages/relay/tests/lib/formatters.spec.ts +++ b/packages/relay/tests/lib/formatters.spec.ts @@ -252,6 +252,11 @@ describe('Formatters', () => { expect(formattedResult.yParity).to.equal('0x0'); expect(formattedResult.value).to.equal('0x0'); }); + + it('Should not include chainId field for legacy EIP155 transaction (tx.chainId=0x0)', () => { + const formattedResult: any = formatContractResult({ ...contractResult, chain_id: '0x' }); + expect(formattedResult.chainId).to.be.undefined; + }); }); describe('prepend0x', () => { diff --git a/packages/server/tests/acceptance/rpc_batch1.spec.ts b/packages/server/tests/acceptance/rpc_batch1.spec.ts index 4d9733d0b2..68213ff190 100644 --- a/packages/server/tests/acceptance/rpc_batch1.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch1.spec.ts @@ -957,6 +957,26 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { expect(balanceChange.toString()).to.eq(Number(ONE_TINYBAR).toString()); }); + it('should return transaction result with no chainId field for legacy EIP155 transactions (with no chainId i.e. chainId=0x0)', async function () { + const transaction = { + ...defaultLegacyTransactionData, + to: parentContractAddress, + nonce: await relay.getAccountNonce(accounts[1].address, requestId), + gasPrice: await relay.gasPrice(requestId), + }; + const signedTx = await accounts[1].wallet.signTransaction(transaction); + const transactionHash = await relay.sendRawTransaction(signedTx, requestId); + + const transactionResult = await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, + [transactionHash], + requestId, + ); + + const result = Object.prototype.hasOwnProperty.call(transactionResult, 'chainId'); + expect(result).to.be.false; + }); + it('should fail "eth_sendRawTransaction" for Legacy transactions (with gas price too low)', async function () { const transaction = { ...defaultLegacyTransactionData,