ox 0.14.51 → 0.14.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/_cjs/core/Transaction.js +8 -0
  3. package/_cjs/core/Transaction.js.map +1 -1
  4. package/_cjs/core/TransactionRequest.js +5 -0
  5. package/_cjs/core/TransactionRequest.js.map +1 -1
  6. package/_cjs/core/TxEnvelopeEip8141.js +37 -8
  7. package/_cjs/core/TxEnvelopeEip8141.js.map +1 -1
  8. package/_cjs/core/internal/frameNonce.js +57 -0
  9. package/_cjs/core/internal/frameNonce.js.map +1 -0
  10. package/_cjs/version.js +1 -1
  11. package/_esm/core/Transaction.js +8 -0
  12. package/_esm/core/Transaction.js.map +1 -1
  13. package/_esm/core/TransactionRequest.js +5 -0
  14. package/_esm/core/TransactionRequest.js.map +1 -1
  15. package/_esm/core/TxEnvelopeEip8141.js +37 -8
  16. package/_esm/core/TxEnvelopeEip8141.js.map +1 -1
  17. package/_esm/core/internal/frameNonce.js +62 -0
  18. package/_esm/core/internal/frameNonce.js.map +1 -0
  19. package/_esm/version.js +1 -1
  20. package/_types/core/Transaction.d.ts +5 -2
  21. package/_types/core/Transaction.d.ts.map +1 -1
  22. package/_types/core/TransactionRequest.d.ts +5 -2
  23. package/_types/core/TransactionRequest.d.ts.map +1 -1
  24. package/_types/core/TxEnvelopeEip8141.d.ts +18 -7
  25. package/_types/core/TxEnvelopeEip8141.d.ts.map +1 -1
  26. package/_types/core/internal/frameNonce.d.ts +21 -0
  27. package/_types/core/internal/frameNonce.d.ts.map +1 -0
  28. package/_types/version.d.ts +1 -1
  29. package/core/Transaction.ts +17 -0
  30. package/core/TransactionRequest.ts +9 -0
  31. package/core/TxEnvelopeEip8141.ts +61 -13
  32. package/core/internal/frameNonce.ts +70 -0
  33. package/package.json +1 -1
  34. package/version.ts +1 -1
@@ -6,11 +6,17 @@ import * as Frame from './Frame.js'
6
6
  import * as FrameSignature from './FrameSignature.js'
7
7
  import * as Hash from './Hash.js'
8
8
  import * as Hex from './Hex.js'
9
+ import * as FrameNonce from './internal/frameNonce.js'
9
10
  import * as Quantity from './internal/quantity.js'
10
11
  import type { Assign, Compute, PartialBy } from './internal/types.js'
11
12
  import * as Rlp from './Rlp.js'
12
13
 
13
- /** An EIP-8141 transaction containing independently budgeted call frames. */
14
+ /**
15
+ * An EIP-8141 transaction containing independently budgeted call frames.
16
+ *
17
+ * Omitted `nonceKeys` preserves the original EIP-8141 encoding. Supplying keys,
18
+ * including `[0n]`, selects EIP-8250 encoding and changes the signing hash.
19
+ */
14
20
  export type TxEnvelopeEip8141 = {
15
21
  /** Versioned blob hashes. @default [] */
16
22
  blobVersionedHashes?: readonly Hex.Hex[] | undefined
@@ -24,8 +30,10 @@ export type TxEnvelopeEip8141 = {
24
30
  maxFeePerGas?: bigint | undefined
25
31
  /** Maximum priority fee per gas, in wei. @default 0n */
26
32
  maxPriorityFeePerGas?: bigint | undefined
27
- /** Sender nonce. @default 0n */
33
+ /** Sender nonce or shared sequence. With `nonceKeys`, the sequence must be less than 2^64 - 1. @default 0n */
28
34
  nonce?: bigint | undefined
35
+ /** EIP-8250 domains: 1–16 strictly increasing uint256 keys. `[0n]` selects the legacy account nonce; zero cannot accompany other keys. */
36
+ nonceKeys?: readonly bigint[] | undefined
29
37
  /** Account authorizing execution. */
30
38
  sender: Address.Address
31
39
  /** PeerDAS sidecars. Excluded from transaction and signing hashes. */
@@ -58,8 +66,10 @@ export type Rpc = {
58
66
  maxFeePerGas: Hex.Hex
59
67
  /** Maximum priority fee per gas. */
60
68
  maxPriorityFeePerGas: Hex.Hex
61
- /** Sender nonce. */
69
+ /** Sender nonce or shared EIP-8250 sequence. */
62
70
  nonce: Hex.Hex
71
+ /** EIP-8250 domains. Omitted for the original EIP-8141 encoding. */
72
+ nonceKeys?: readonly Hex.Hex[] | undefined
63
73
  /** Frame signature entries. */
64
74
  signatures: readonly FrameSignature.Rpc[]
65
75
  /** RPC transaction type. */
@@ -111,6 +121,7 @@ export function assert(envelope: PartialBy<TxEnvelopeEip8141, 'type'>): void {
111
121
  maxFeePerGas = 0n,
112
122
  maxPriorityFeePerGas = 0n,
113
123
  nonce = 0n,
124
+ nonceKeys,
114
125
  sender,
115
126
  signatures = [],
116
127
  } = envelope
@@ -123,6 +134,7 @@ export function assert(envelope: PartialBy<TxEnvelopeEip8141, 'type'>): void {
123
134
  throw new InvalidError(
124
135
  'chainId must be an unsigned 256-bit integer; use bigint for large IDs.',
125
136
  )
137
+ if (nonceKeys !== undefined) FrameNonce.assert(nonceKeys, nonce)
126
138
  Address.assert(sender, { strict: false })
127
139
  for (const [field, value, bits] of [
128
140
  ['maxFeePerBlobGas', maxFeePerBlobGas, 256n],
@@ -162,6 +174,18 @@ export function assert(envelope: PartialBy<TxEnvelopeEip8141, 'type'>): void {
162
174
  for (const byte of Hex.toBytes(data)) tokens += byte === 0 ? 1n : 4n
163
175
  size += BigInt(Hex.size(data))
164
176
  }
177
+ if (nonceKeys !== undefined) {
178
+ count(
179
+ Rlp.fromHex(
180
+ nonceKeys.map((key) =>
181
+ key === 0n ? '0x' : Hex.fromBytes(Bytes.fromNumber(key)),
182
+ ),
183
+ ),
184
+ )
185
+ count(
186
+ Rlp.fromHex(nonce === 0n ? '0x' : Hex.fromBytes(Bytes.fromNumber(nonce))),
187
+ )
188
+ }
165
189
  for (const [index, frame] of (frames as readonly Frame.Frame[]).entries()) {
166
190
  Frame.assert(frame)
167
191
  const flags =
@@ -226,6 +250,7 @@ export function assert(envelope: PartialBy<TxEnvelopeEip8141, 'type'>): void {
226
250
  export declare namespace assert {
227
251
  type ErrorType =
228
252
  | InvalidError
253
+ | FrameNonce.InvalidError
229
254
  | Frame.assert.ErrorType
230
255
  | FrameSignature.toTuple.ErrorType
231
256
  | Address.assert.ErrorType
@@ -263,9 +288,14 @@ export function deserialize(serialized: Serialized): TxEnvelopeEip8141 {
263
288
  throw new InvalidError('Expected a transaction list.')
264
289
  const wrapped = Array.isArray(decoded[0])
265
290
  const body = wrapped ? decoded[0] : decoded
266
- if (!Array.isArray(body) || body.length !== 7)
267
- throw new InvalidError('Expected seven transaction fields.')
268
- const [chainId, nonce, sender, frames, signatures, fees, hashes] = body
291
+ if (!Array.isArray(body) || (body.length !== 7 && body.length !== 8))
292
+ throw new InvalidError('Expected seven or eight transaction fields.')
293
+ const keyed = body.length === 8
294
+ const [chainId, ...fields] = body
295
+ const keys = keyed ? fields.shift() : undefined
296
+ if (keyed && !Array.isArray(keys))
297
+ throw new InvalidError('Expected a nonce key list.')
298
+ const [nonce, sender, frames, signatures, fees, hashes] = fields
269
299
  if (
270
300
  typeof sender !== 'string' ||
271
301
  !Array.isArray(frames) ||
@@ -293,6 +323,7 @@ export function deserialize(serialized: Serialized): TxEnvelopeEip8141 {
293
323
  maxFeePerGas: integer(fees[1]),
294
324
  maxPriorityFeePerGas: integer(fees[0]),
295
325
  nonce: integer(nonce),
326
+ ...(Array.isArray(keys) ? { nonceKeys: keys.map(integer) } : {}),
296
327
  sender: sender as Address.Address,
297
328
  signatures: signatures.map((entry) =>
298
329
  FrameSignature.fromTuple(entry as FrameSignature.Tuple),
@@ -408,6 +439,9 @@ export function fromRpc(envelope: Rpc): TxEnvelopeEip8141 {
408
439
  maxFeePerGas: Hex.toBigInt(envelope.maxFeePerGas),
409
440
  maxPriorityFeePerGas: Hex.toBigInt(envelope.maxPriorityFeePerGas),
410
441
  nonce: Hex.toBigInt(envelope.nonce),
442
+ ...(envelope.nonceKeys !== undefined
443
+ ? { nonceKeys: FrameNonce.fromRpc(envelope.nonceKeys, envelope.nonce) }
444
+ : {}),
411
445
  sender: envelope.from,
412
446
  signatures: envelope.signatures.map(FrameSignature.fromRpc),
413
447
  })
@@ -415,6 +449,7 @@ export function fromRpc(envelope: Rpc): TxEnvelopeEip8141 {
415
449
 
416
450
  export declare namespace fromRpc {
417
451
  type ErrorType =
452
+ | FrameNonce.InvalidError
418
453
  | from.ErrorType
419
454
  | Frame.fromRpc.ErrorType
420
455
  | FrameSignature.fromRpc.ErrorType
@@ -453,6 +488,9 @@ export function toRpc(envelope: toRpc.Input): Rpc {
453
488
  envelope.maxPriorityFeePerGas ?? 0n,
454
489
  ),
455
490
  nonce: Quantity.fromNumberish(envelope.nonce ?? 0n),
491
+ ...(envelope.nonceKeys !== undefined
492
+ ? { nonceKeys: FrameNonce.toRpc(envelope.nonceKeys, envelope.nonce) }
493
+ : {}),
456
494
  signatures: (envelope.signatures ?? []).map(FrameSignature.toRpc),
457
495
  type: '0x6',
458
496
  }
@@ -467,6 +505,7 @@ export declare namespace toRpc {
467
505
  | 'maxFeePerGas'
468
506
  | 'maxPriorityFeePerGas'
469
507
  | 'nonce'
508
+ | 'nonceKeys'
470
509
  | 'type'
471
510
  > & {
472
511
  chainId: Hex.Hex | bigint | number
@@ -475,9 +514,11 @@ export declare namespace toRpc {
475
514
  maxFeePerGas?: Hex.Hex | bigint | number | undefined
476
515
  maxPriorityFeePerGas?: Hex.Hex | bigint | number | undefined
477
516
  nonce?: Hex.Hex | bigint | number | undefined
517
+ nonceKeys?: readonly (Hex.Hex | bigint | number)[] | undefined
478
518
  type?: Type | undefined
479
519
  }
480
520
  type ErrorType =
521
+ | FrameNonce.InvalidError
481
522
  | Frame.toRpc.ErrorType
482
523
  | FrameSignature.toRpc.ErrorType
483
524
  | Hex.fromNumber.ErrorType
@@ -565,10 +606,12 @@ export function hash(
565
606
  ): Hex.Hex {
566
607
  assert(envelope)
567
608
  const body = toTuple(envelope)
568
- if (options.presign)
569
- body[4] = body[4].map((entry) =>
570
- entry[2] === '0x' ? [entry[0], entry[1], entry[2], '0x'] : entry,
571
- )
609
+ if (options.presign) {
610
+ const signatures = body.length === 8 ? body[5] : body[4]
611
+ for (const [index, entry] of signatures.entries())
612
+ if (entry[2] === '0x')
613
+ signatures[index] = [entry[0], entry[1], entry[2], '0x']
614
+ }
572
615
  return Hash.keccak256(Hex.concat(serializedType, Rlp.fromHex(body)))
573
616
  }
574
617
  export declare namespace hash {
@@ -692,8 +735,7 @@ export class InvalidError extends Errors.BaseError {
692
735
  function toTuple(envelope: PartialBy<TxEnvelopeEip8141, 'type'>) {
693
736
  const quantity = (value: bigint | number | undefined): Hex.Hex =>
694
737
  value ? Hex.fromBytes(Bytes.fromNumber(value)) : '0x'
695
- return [
696
- quantity(envelope.chainId),
738
+ const fields = [
697
739
  quantity(envelope.nonce),
698
740
  envelope.sender,
699
741
  envelope.frames.map((frame) => Frame.toTuple(frame)),
@@ -705,7 +747,6 @@ function toTuple(envelope: PartialBy<TxEnvelopeEip8141, 'type'>) {
705
747
  ],
706
748
  envelope.blobVersionedHashes ?? [],
707
749
  ] satisfies [
708
- Hex.Hex,
709
750
  Hex.Hex,
710
751
  Address.Address,
711
752
  Frame.Tuple[],
@@ -713,4 +754,11 @@ function toTuple(envelope: PartialBy<TxEnvelopeEip8141, 'type'>) {
713
754
  Hex.Hex[],
714
755
  readonly Hex.Hex[],
715
756
  ]
757
+ return envelope.nonceKeys === undefined
758
+ ? ([quantity(envelope.chainId), ...fields] as const)
759
+ : ([
760
+ quantity(envelope.chainId),
761
+ envelope.nonceKeys.map(quantity),
762
+ ...fields,
763
+ ] as const)
716
764
  }
@@ -0,0 +1,70 @@
1
+ import * as Errors from '../Errors.js'
2
+ import * as Hex from '../Hex.js'
3
+ import * as Quantity from './quantity.js'
4
+
5
+ /**
6
+ * Asserts keyed nonce constraints.
7
+ * @internal
8
+ */
9
+ export function assert(keys: readonly bigint[], nonce?: bigint): void {
10
+ if (!Array.isArray(keys) || keys.length < 1 || keys.length > 16)
11
+ throw new InvalidError('Expected between 1 and 16 nonce keys.')
12
+ for (const [index, key] of keys.entries()) {
13
+ if (typeof key !== 'bigint' || key < 0n || key >= 2n ** 256n)
14
+ throw new InvalidError('Nonce keys must be unsigned 256-bit integers.')
15
+ if (index > 0 && key <= keys[index - 1]!)
16
+ throw new InvalidError('Nonce keys must be strictly increasing.')
17
+ }
18
+ if (keys.length > 1 && keys[0] === 0n)
19
+ throw new InvalidError(
20
+ 'The zero nonce key cannot be combined with other keys.',
21
+ )
22
+ if (
23
+ nonce !== undefined &&
24
+ (typeof nonce !== 'bigint' || nonce < 0n || nonce >= 2n ** 64n - 1n)
25
+ )
26
+ throw new InvalidError('Nonce must be less than 2^64 - 1 and nonnegative.')
27
+ }
28
+
29
+ /**
30
+ * Decodes and validates RPC nonce keys and their shared sequence.
31
+ * @internal
32
+ */
33
+ export function fromRpc(
34
+ keys: readonly Hex.Hex[],
35
+ nonce?: Hex.Hex,
36
+ ): readonly bigint[] {
37
+ if (!Array.isArray(keys)) throw new InvalidError('Expected a nonce key list.')
38
+ const integer = (value: Hex.Hex): bigint => {
39
+ if (
40
+ typeof value !== 'string' ||
41
+ !/^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$/.test(value)
42
+ )
43
+ throw new InvalidError('Expected a canonical RPC nonce quantity.')
44
+ return Hex.toBigInt(value)
45
+ }
46
+ const decoded = keys.map(integer)
47
+ assert(decoded, nonce === undefined ? undefined : integer(nonce))
48
+ return decoded
49
+ }
50
+
51
+ /**
52
+ * Encodes and validates nonce keys and their shared sequence.
53
+ * @internal
54
+ */
55
+ export function toRpc(
56
+ keys: readonly (Hex.Hex | bigint | number)[],
57
+ nonce?: Hex.Hex | bigint | number,
58
+ ): readonly Hex.Hex[] {
59
+ if (!Array.isArray(keys)) throw new InvalidError('Expected a nonce key list.')
60
+ const encoded = keys.map((key) => Quantity.fromNumberish(key))
61
+ fromRpc(
62
+ encoded,
63
+ nonce === undefined ? undefined : Quantity.fromNumberish(nonce),
64
+ )
65
+ return encoded
66
+ }
67
+
68
+ export class InvalidError extends Errors.BaseError {
69
+ override readonly name = 'FrameNonce.InvalidError'
70
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ox",
3
3
  "description": "Ethereum Standard Library",
4
- "version": "0.14.51",
4
+ "version": "0.14.52",
5
5
  "main": "./_cjs/index.js",
6
6
  "module": "./_esm/index.js",
7
7
  "types": "./_types/index.d.ts",
package/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** @internal */
2
- export const version = '0.14.51'
2
+ export const version = '0.14.52'