Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/frame-rpc-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish the RPC migration as a major change

This commit removes or changes several public RPC fields and types—such as executionGasLimit, numeric frame modes, and the previous FrameReceipt shape—yet the changeset declares only a patch release. Consumers accepting a patch update can therefore encounter compile failures or incompatible RPC payloads; mark this as a major change and include the required before/after migration shape.

AGENTS.md reference: AGENTS.md:L121-L124

Useful? React with 👍 / 👎.

---

Aligned frame RPC quantities, signature placeholders, and receipt gas totals with the execution API definitions.
37 changes: 20 additions & 17 deletions src/core/Frame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ export type Rpc = {
/** Frame calldata. */
data: Hex.Hex
/** Execution gas budget. */
executionGasLimit: Hex.Hex
executionGas: Hex.Hex

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the guide for the renamed RPC frame fields

After this rename, site/src/pages/guides/transactions/eip-8141.mdx:120-124 still says toRpc emits executionGasLimit/stateGasLimit and that modes, flags, and signature schemes are numbers. Users following that handwritten guide will construct the obsolete payload shape that the updated codecs reject, so update the paragraph alongside these RPC changes.

Useful? React with 👍 / 👎.

/** Approval scope and batching bits. */
flags: number
flags: Hex.Hex
/** Execution mode. */
mode: number
mode: Hex.Hex
/** State gas budget. */
stateGasLimit: Hex.Hex
stateGas: Hex.Hex
/** Target address; absent for the transaction sender. */
target?: Address.Address | null | undefined
/** Value transferred in wei. */
Expand Down Expand Up @@ -205,10 +205,10 @@ export declare namespace from {
*
* const frame = Frame.fromRpc({
* data: '0x',
* executionGasLimit: '0xc350',
* flags: 3,
* mode: 1,
* stateGasLimit: '0x0',
* executionGas: '0xc350',
* flags: '0x3',
* mode: '0x1',
* stateGas: '0x0',
* value: '0x0'
* })
* ```
Expand All @@ -219,17 +219,20 @@ export declare namespace from {
export function fromRpc(frame: Rpc): Frame {
return from({
data: frame.data,
executionGas: Hex.toBigInt(frame.executionGasLimit),
flags: frame.flags,
mode: frame.mode,
stateGas: Hex.toBigInt(frame.stateGasLimit),
executionGas: Hex.toBigInt(frame.executionGas),
flags: Hex.toNumber(frame.flags),
mode: Hex.toNumber(frame.mode),
stateGas: Hex.toBigInt(frame.stateGas),
...(frame.target == null ? {} : { to: frame.target }),
value: Hex.toBigInt(frame.value),
})
}

export declare namespace fromRpc {
type ErrorType = from.ErrorType | Hex.toBigInt.ErrorType
type ErrorType =
| from.ErrorType
| Hex.toBigInt.ErrorType
| Hex.toNumber.ErrorType
}

/**
Expand All @@ -255,10 +258,10 @@ export function toRpc(frame: toRpc.Input): Rpc {
const { flags: flags_ = 0, mode = 0 } = frame
return {
data: frame.data ?? '0x',
executionGasLimit: Quantity.fromNumberish(frame.executionGas ?? 0n),
flags: typeof flags_ === 'string' ? flags[flags_] : flags_,
mode: typeof mode === 'string' ? modes[mode] : mode,
stateGasLimit: Quantity.fromNumberish(frame.stateGas ?? 0n),
executionGas: Quantity.fromNumberish(frame.executionGas ?? 0n),
flags: Hex.fromNumber(typeof flags_ === 'string' ? flags[flags_] : flags_),
mode: Hex.fromNumber(typeof mode === 'string' ? modes[mode] : mode),
stateGas: Quantity.fromNumberish(frame.stateGas ?? 0n),
...(frame.to === undefined ? {} : { target: frame.to }),
value: Quantity.fromNumberish(frame.value ?? 0n),
}
Expand Down
46 changes: 30 additions & 16 deletions src/core/FrameReceipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ import type * as Errors from './Errors.js'
import * as Hex from './Hex.js'
import * as Quantity from './internal/quantity.js'
import type * as Log from './Log.js'
import type { Compute, ExactPartial } from './internal/types.js'

/** Receipt for one frame. Frame logs contain only address, data, and topics. */
type FrameLog = Compute<
Pick<Log.Rpc, 'address' | 'data' | 'topics'> &
ExactPartial<Omit<Log.Rpc, 'address' | 'data' | 'topics'>>
Comment on lines +8 to +9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Decode the newly exposed frame-log quantities

When a frame log includes the metadata newly admitted here, FrameReceipt.fromRpc copies receipt.logs unchanged, so blockNumber, logIndex, and transactionIndex remain hex strings; the decoded Zod schema likewise uses quantityHex() for them. Consequently TransactionReceipt.fromRpc produces bigint/number fields in its ordinary logs array but RPC-shaped strings for the same fields under frameReceipts[].logs, making numeric consumers behave inconsistently. Use a decoded frame-log shape and convert each present metadata quantity in both directions.

Useful? React with 👍 / 👎.

>

/** Receipt for one frame. */
export type FrameReceipt<bigintType = bigint> = {
/** Execution gas used before transaction-level refunds. */
executionGasUsed: bigintType
/** Total execution and state gas used. */
gasUsed: bigintType
/** Logs emitted by this frame. */
logs: readonly Pick<Log.Log, 'address' | 'data' | 'topics'>[]
logs: readonly FrameLog[]
/** Final state gas after refills and rollbacks. */
stateGasUsed: bigintType
/** Frame execution result. */
Expand All @@ -19,8 +27,10 @@ export type FrameReceipt<bigintType = bigint> = {
export type Rpc = {
/** Execution gas used. */
executionGasUsed: Hex.Hex
/** Total execution and state gas used. */
gasUsed: Hex.Hex
/** Logs emitted by this frame. */
logs: readonly Pick<Log.Log, 'address' | 'data' | 'topics'>[]
logs: readonly FrameLog[]
/** Final state gas used. */
stateGasUsed: Hex.Hex
/** Zero for failure, one for success, or two for a skipped frame. */
Expand All @@ -30,21 +40,21 @@ export type Rpc = {
/** Frame execution status. */
export type Status = 'reverted' | 'skipped' | 'success'

/** Numeric JSON-RPC frame execution status. */
export type RpcStatus = 0 | 1 | 2
/** Hex JSON-RPC frame execution status. */
export type RpcStatus = '0x0' | '0x1' | '0x2'

/** RPC status to status mapping. */
export const fromRpcStatus = {
0: 'reverted',
1: 'success',
2: 'skipped',
'0x0': 'reverted',
'0x1': 'success',
'0x2': 'skipped',
} as const

/** Status to RPC status mapping. */
export const toRpcStatus = {
reverted: 0,
skipped: 2,
success: 1,
reverted: '0x0',
skipped: '0x2',
success: '0x1',
} as const

/**
Expand All @@ -58,9 +68,10 @@ export const toRpcStatus = {
*
* const receipt = FrameReceipt.fromRpc({
* executionGasUsed: '0x5208',
* gasUsed: '0x5208',
* logs: [],
* stateGasUsed: '0x0',
* status: 1
* status: '0x1'
* })
* ```
*
Expand All @@ -69,7 +80,8 @@ export const toRpcStatus = {
*/
export function fromRpc(receipt: Rpc): FrameReceipt {
return {
gasUsed: Hex.toBigInt(receipt.executionGasUsed),
executionGasUsed: Hex.toBigInt(receipt.executionGasUsed),
gasUsed: Hex.toBigInt(receipt.gasUsed),
logs: receipt.logs,
stateGasUsed: Hex.toBigInt(receipt.stateGasUsed),
status: fromRpcStatus[receipt.status],
Expand All @@ -91,19 +103,21 @@ export declare namespace fromRpc {
*
* const receipt = FrameReceipt.fromRpc({
* executionGasUsed: '0x5208',
* gasUsed: '0x5208',
* logs: [],
* stateGasUsed: '0x0',
* status: 1
* status: '0x1'
* })
* const rpc = FrameReceipt.toRpc(receipt)
* ```
*
* @param receipt - The frame receipt. Gas accepts hex, bigint, or number values.
* @returns The RPC frame receipt with hex gas and a numeric status.
* @returns The RPC frame receipt with hex gas and a hex status.
*/
export function toRpc(receipt: toRpc.Input): Rpc {
return {
executionGasUsed: Quantity.fromNumberish(receipt.gasUsed),
executionGasUsed: Quantity.fromNumberish(receipt.executionGasUsed),
gasUsed: Quantity.fromNumberish(receipt.gasUsed),
logs: receipt.logs,
stateGasUsed: Quantity.fromNumberish(receipt.stateGasUsed),
status: toRpcStatus[receipt.status],
Expand Down
27 changes: 17 additions & 10 deletions src/core/FrameSignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ export type FrameSignature = Arbitrary | Secp256k1 | P256
/** JSON-RPC representation of a frame signature. */
export type Rpc = {
/** Explicit digest, or empty bytes for the transaction signing hash. */
msg: Hex.Hex
msg?: Hex.Hex | undefined
/** Signature verification scheme. */
scheme: 0 | 1 | 2
scheme: '0x0' | '0x1' | '0x2'
/** Encoded signature bytes. */
signature: Hex.Hex
signature?: Hex.Hex | undefined
/** Signer address; absent for the transaction sender. */
signer?: Address.Address | null | undefined
}
Expand Down Expand Up @@ -348,7 +348,7 @@ export declare namespace from {
*
* const entry = FrameSignature.fromRpc({
* msg: '0x',
* scheme: 0,
* scheme: '0x0',
* signature: '0xdeadbeef'
* })
* ```
Expand All @@ -358,15 +358,20 @@ export declare namespace from {
*/
export function fromRpc(entry: Rpc): FrameSignature {
return fromTuple([
entry.scheme === 0 ? '0x' : Hex.fromNumber(entry.scheme, { size: 1 }),
entry.scheme === '0x0'
? '0x'
: Hex.fromNumber(Hex.toNumber(entry.scheme), { size: 1 }),
entry.signer ?? '0x',
entry.msg,
entry.signature,
entry.msg ?? '0x',
entry.signature ?? '0x',
])
}

export declare namespace fromRpc {
type ErrorType = fromTuple.ErrorType | Hex.fromNumber.ErrorType
type ErrorType =
| fromTuple.ErrorType
| Hex.fromNumber.ErrorType
| Hex.toNumber.ErrorType
}

/**
Expand All @@ -389,8 +394,10 @@ export function toRpc(entry: FrameSignature): Rpc {
const [scheme, signer, msg, signature] = toTuple(entry)
return {
msg,
scheme: (scheme === '0x' ? 0 : Hex.toNumber(scheme)) as Rpc['scheme'],
signature,
scheme: (scheme === '0x'
? '0x0'
: Hex.fromNumber(Hex.toNumber(scheme))) as Rpc['scheme'],
...(signature === '0x' && scheme !== '0x' ? {} : { signature }),
...(signer === '0x' ? {} : { signer }),
}
}
Expand Down
59 changes: 35 additions & 24 deletions src/core/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import * as Frame from './Frame.js'
import * as FrameSignature from './FrameSignature.js'
import * as Hex from './Hex.js'
import * as Quantity from './internal/quantity.js'
import type { Compute, OneOf, UnionCompute } from './internal/types.js'
import type {
Compute,
ExactPartial,
OneOf,
UnionCompute,
} from './internal/types.js'
import * as Signature from './Signature.js'

/**
Expand Down Expand Up @@ -197,29 +202,35 @@ export type Eip8141<
> = Compute<
Omit<
Base<type, pending, bigintType, numberType>,
'chainId' | 'r' | 's' | 'v' | 'yParity'
> & {
/** Versioned blob hashes. */
blobVersionedHashes: readonly Hex.Hex[]
/** Chain ID, retaining bigint precision when needed. */
chainId: numberType | bigintType
/** Frames in execution order. */
frames: readonly ([bigintType] extends [Hex.Hex]
? Frame.Rpc
: Frame.Frame<bigintType>)[]
/** Effective gas price. */
gasPrice?: bigintType | undefined
/** Maximum fee per blob gas. */
maxFeePerBlobGas: bigintType
/** Maximum fee per gas. */
maxFeePerGas: bigintType
/** Maximum priority fee per gas. */
maxPriorityFeePerGas: bigintType
/** Frame signature entries. */
signatures: readonly ([bigintType] extends [Hex.Hex]
? FrameSignature.Rpc
: FrameSignature.FrameSignature)[]
}
'chainId' | 'gas' | 'input' | 'to' | 'value' | 'r' | 's' | 'v' | 'yParity'
> &
ExactPartial<
Pick<
Base<type, pending, bigintType, numberType>,
'gas' | 'input' | 'to' | 'value'
Comment on lines +205 to +210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve omitted EIP-8141 aggregate fields in codecs

When an EIP-8141 RPC response omits the newly optional gas or value fields, Transaction.fromRpc still unconditionally materializes them as 0n, and Transaction.toRpc then emits gas: '0x0' and value: '0x0'; input and to are similarly assigned explicitly rather than preserved as absent. This loses the distinction introduced by making these fields optional and makes a response that legitimately omits aggregate transaction fields fail to round-trip, so the conversion paths should special-case EIP-8141 and only map fields that are present.

Useful? React with 👍 / 👎.

>
> & {
/** Versioned blob hashes. */
blobVersionedHashes: readonly Hex.Hex[]
/** Chain ID, retaining bigint precision when needed. */
chainId: numberType | bigintType
/** Frames in execution order. */
frames: readonly ([bigintType] extends [Hex.Hex]
? Frame.Rpc
: Frame.Frame<bigintType>)[]
/** Effective gas price. */
gasPrice?: bigintType | undefined
/** Maximum fee per blob gas. */
maxFeePerBlobGas: bigintType
/** Maximum fee per gas. */
maxFeePerGas: bigintType
/** Maximum priority fee per gas. */
maxPriorityFeePerGas: bigintType
/** Frame signature entries. */
signatures: readonly ([bigintType] extends [Hex.Hex]
? FrameSignature.Rpc
: FrameSignature.FrameSignature)[]
}
>

/** An EIP-8141 RPC transaction. */
Expand Down
2 changes: 2 additions & 0 deletions src/core/_test/AbiConstructor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ describe('decode', () => {
params: [hash],
}))!

if (input === undefined) throw new Error('Missing transaction input')

expect(
AbiConstructor.decode(abiConstructor, {
bytecode: Constructor.bytecode.object,
Expand Down
24 changes: 12 additions & 12 deletions src/core/_test/Frame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,10 @@ describe('fromRpc', () => {
expect(
Frame.fromRpc({
data: '0xdeadbeef',
executionGasLimit: '0x20000000000001',
flags: 0,
mode: 2,
stateGasLimit: '0xc',
executionGas: '0x20000000000001',
flags: '0x0',
mode: '0x2',
stateGas: '0xc',
target: null,
value: '0x1',
}),
Expand All @@ -273,10 +273,10 @@ describe('toRpc', () => {
),
).toEqual({
data: '0x',
executionGasLimit: '0x0',
flags: 3,
mode: 1,
stateGasLimit: '0x0',
executionGas: '0x0',
flags: '0x3',
mode: '0x1',
stateGas: '0x0',
value: '0x0',
})
})
Expand All @@ -285,10 +285,10 @@ describe('toRpc', () => {
Frame.toRpc({ executionGas: '0xc350', stateGas: 12, value: 0n }),
).toEqual({
data: '0x',
executionGasLimit: '0xc350',
flags: 0,
mode: 0,
stateGasLimit: '0xc',
executionGas: '0xc350',
flags: '0x0',
mode: '0x0',
stateGas: '0xc',
value: '0x0',
})
})
Expand Down
Loading
Loading