ox 1.8.1 → 1.8.4

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 (152) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/core/Frame.d.ts +23 -20
  3. package/dist/core/Frame.d.ts.map +1 -1
  4. package/dist/core/Frame.js +28 -23
  5. package/dist/core/Frame.js.map +1 -1
  6. package/dist/core/FrameReceipt.d.ts +23 -14
  7. package/dist/core/FrameReceipt.d.ts.map +1 -1
  8. package/dist/core/FrameReceipt.js +15 -11
  9. package/dist/core/FrameReceipt.js.map +1 -1
  10. package/dist/core/FrameRequest.d.ts +50 -0
  11. package/dist/core/FrameRequest.d.ts.map +1 -0
  12. package/dist/core/FrameRequest.js +59 -0
  13. package/dist/core/FrameRequest.js.map +1 -0
  14. package/dist/core/FrameSignature.d.ts +17 -16
  15. package/dist/core/FrameSignature.d.ts.map +1 -1
  16. package/dist/core/FrameSignature.js +38 -21
  17. package/dist/core/FrameSignature.js.map +1 -1
  18. package/dist/core/Siwe.d.ts.map +1 -1
  19. package/dist/core/Siwe.js +3 -3
  20. package/dist/core/Siwe.js.map +1 -1
  21. package/dist/core/Transaction.d.ts +7 -4
  22. package/dist/core/Transaction.d.ts.map +1 -1
  23. package/dist/core/Transaction.js +8 -0
  24. package/dist/core/Transaction.js.map +1 -1
  25. package/dist/core/TransactionRequest.d.ts +8 -5
  26. package/dist/core/TransactionRequest.d.ts.map +1 -1
  27. package/dist/core/TransactionRequest.js +11 -3
  28. package/dist/core/TransactionRequest.js.map +1 -1
  29. package/dist/core/TxEnvelope.d.ts +4 -2
  30. package/dist/core/TxEnvelope.d.ts.map +1 -1
  31. package/dist/core/TxEnvelope.js +11 -2
  32. package/dist/core/TxEnvelope.js.map +1 -1
  33. package/dist/core/TxEnvelopeEip8141.d.ts +22 -11
  34. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -1
  35. package/dist/core/TxEnvelopeEip8141.js +45 -16
  36. package/dist/core/TxEnvelopeEip8141.js.map +1 -1
  37. package/dist/core/internal/frameNonce.d.ts +10 -0
  38. package/dist/core/internal/frameNonce.d.ts.map +1 -0
  39. package/dist/core/internal/frameNonce.js +45 -0
  40. package/dist/core/internal/frameNonce.js.map +1 -0
  41. package/dist/core/internal/rpcSchemas/eth.d.ts +16 -4
  42. package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
  43. package/dist/index.d.ts +21 -5
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +21 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/zod/Block.d.ts +52 -37
  48. package/dist/zod/Block.d.ts.map +1 -1
  49. package/dist/zod/Frame.d.ts +14 -14
  50. package/dist/zod/Frame.js +5 -5
  51. package/dist/zod/Frame.js.map +1 -1
  52. package/dist/zod/FrameReceipt.d.ts +42 -2
  53. package/dist/zod/FrameReceipt.d.ts.map +1 -1
  54. package/dist/zod/FrameReceipt.js +11 -1
  55. package/dist/zod/FrameReceipt.js.map +1 -1
  56. package/dist/zod/FrameRequest.d.ts +40 -0
  57. package/dist/zod/FrameRequest.d.ts.map +1 -0
  58. package/dist/zod/FrameRequest.js +37 -0
  59. package/dist/zod/FrameRequest.js.map +1 -0
  60. package/dist/zod/FrameSignature.d.ts +21 -13
  61. package/dist/zod/FrameSignature.d.ts.map +1 -1
  62. package/dist/zod/FrameSignature.js +10 -7
  63. package/dist/zod/FrameSignature.js.map +1 -1
  64. package/dist/zod/RpcSchema.d.ts +470 -265
  65. package/dist/zod/RpcSchema.d.ts.map +1 -1
  66. package/dist/zod/Transaction.d.ts +104 -74
  67. package/dist/zod/Transaction.d.ts.map +1 -1
  68. package/dist/zod/Transaction.js +22 -15
  69. package/dist/zod/Transaction.js.map +1 -1
  70. package/dist/zod/TransactionEnvelope.d.ts +60 -34
  71. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  72. package/dist/zod/TransactionReceipt.d.ts +42 -2
  73. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  74. package/dist/zod/TransactionRequest.d.ts +31 -21
  75. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  76. package/dist/zod/TransactionRequest.js +10 -4
  77. package/dist/zod/TransactionRequest.js.map +1 -1
  78. package/dist/zod/TxEnvelopeEip8141.d.ts +44 -25
  79. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
  80. package/dist/zod/TxEnvelopeEip8141.js +2 -0
  81. package/dist/zod/TxEnvelopeEip8141.js.map +1 -1
  82. package/dist/zod/internal/Frame.d.ts +8 -0
  83. package/dist/zod/internal/Frame.d.ts.map +1 -1
  84. package/dist/zod/internal/Frame.js +29 -0
  85. package/dist/zod/internal/Frame.js.map +1 -1
  86. package/dist/zod/internal/rpcSchemas/Eth.d.ts +472 -267
  87. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  88. package/dist/zod/internal/rpcSchemas/Eth.js +9 -0
  89. package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
  90. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +51 -21
  91. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  92. package/dist/zod/tempo/RpcSchemaTempo.d.ts +68 -48
  93. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  94. package/dist/zod/tempo/Transaction.d.ts +52 -37
  95. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  96. package/dist/zod/z.d.ts +1 -0
  97. package/dist/zod/z.d.ts.map +1 -1
  98. package/dist/zod/z.js +1 -0
  99. package/dist/zod/z.js.map +1 -1
  100. package/package.json +11 -1
  101. package/src/core/Frame.ts +41 -33
  102. package/src/core/FrameReceipt.ts +30 -16
  103. package/src/core/FrameRequest.ts +78 -0
  104. package/src/core/FrameSignature.ts +64 -34
  105. package/src/core/Siwe.ts +21 -13
  106. package/src/core/Transaction.ts +52 -24
  107. package/src/core/TransactionRequest.ts +21 -7
  108. package/src/core/TxEnvelope.ts +16 -3
  109. package/src/core/TxEnvelopeEip8141.ts +69 -21
  110. package/src/core/_test/AbiConstructor.test.ts +2 -0
  111. package/src/core/_test/Frame.test-d.ts +16 -7
  112. package/src/core/_test/Frame.test.ts +52 -24
  113. package/src/core/_test/FrameReceipt.test.ts +15 -9
  114. package/src/core/_test/FrameRequest.test-d.ts +16 -0
  115. package/src/core/_test/FrameRequest.test.ts +41 -0
  116. package/src/core/_test/FrameRpc.test.ts +131 -0
  117. package/src/core/_test/FrameSignature.test-d.ts +20 -0
  118. package/src/core/_test/FrameSignature.test.ts +132 -11
  119. package/src/core/_test/RpcSchema.test-d.ts +45 -0
  120. package/src/core/_test/Siwe.test.ts +147 -0
  121. package/src/core/_test/TransactionEnvelope.test-d.ts +27 -0
  122. package/src/core/_test/TransactionEnvelope.test.ts +27 -1
  123. package/src/core/_test/TransactionReceipt.test.ts +32 -6
  124. package/src/core/_test/TransactionRequest.test-d.ts +10 -1
  125. package/src/core/_test/TransactionRequest.test.ts +31 -2
  126. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +29 -2
  127. package/src/core/_test/TxEnvelopeEip8141.test.ts +548 -55
  128. package/src/core/_test/index.test.ts +1 -0
  129. package/src/core/internal/frameNonce.ts +61 -0
  130. package/src/core/internal/rpcSchemas/eth.ts +13 -4
  131. package/src/index.ts +22 -5
  132. package/src/version.ts +1 -1
  133. package/src/zod/Frame.ts +5 -5
  134. package/src/zod/FrameReceipt.ts +11 -1
  135. package/src/zod/FrameRequest.ts +46 -0
  136. package/src/zod/FrameSignature.ts +10 -7
  137. package/src/zod/Transaction.ts +46 -27
  138. package/src/zod/TransactionRequest.ts +33 -18
  139. package/src/zod/TxEnvelopeEip8141.ts +2 -0
  140. package/src/zod/_test/Frame.test.ts +13 -11
  141. package/src/zod/_test/FrameReceipt.test.ts +11 -4
  142. package/src/zod/_test/FrameRequest.test-d.ts +15 -0
  143. package/src/zod/_test/FrameRequest.test.ts +44 -0
  144. package/src/zod/_test/FrameSignature.test.ts +73 -9
  145. package/src/zod/_test/RpcSchema.test.ts +106 -0
  146. package/src/zod/_test/TransactionRequest.test.ts +61 -0
  147. package/src/zod/_test/TxEnvelopeEip8141.nonce.test.ts +174 -0
  148. package/src/zod/_test/TxEnvelopeEip8141.test-d.ts +29 -0
  149. package/src/zod/_test/TxEnvelopeEip8141.test.ts +3 -3
  150. package/src/zod/internal/Frame.ts +45 -0
  151. package/src/zod/internal/rpcSchemas/Eth.ts +20 -0
  152. package/src/zod/z.ts +1 -0
@@ -0,0 +1,131 @@
1
+ import {
2
+ Frame,
3
+ FrameSignature,
4
+ TransactionRequest,
5
+ TransactionEnvelope,
6
+ TxEnvelopeEip8141,
7
+ } from 'ox'
8
+ import { describe, expect, test } from 'vp/test'
9
+
10
+ describe('TransactionRequest.toRpc', () => {
11
+ test('preserves omitted frame gas and explicit zero limits', () => {
12
+ expect(
13
+ TransactionRequest.toRpc({
14
+ type: 'eip8141',
15
+ frames: [
16
+ { mode: 'sender' },
17
+ { mode: 'sender', executionGas: 0n },
18
+ { mode: 'sender', stateGas: 0n },
19
+ { mode: 'sender', executionGas: 50_000n, stateGas: 1_000n },
20
+ ],
21
+ }),
22
+ ).toEqual({
23
+ type: '0x6',
24
+ frames: [
25
+ { mode: '0x2', flags: '0x0', data: '0x', value: '0x0' },
26
+ {
27
+ mode: '0x2',
28
+ flags: '0x0',
29
+ data: '0x',
30
+ value: '0x0',
31
+ executionGas: '0x0',
32
+ },
33
+ {
34
+ mode: '0x2',
35
+ flags: '0x0',
36
+ data: '0x',
37
+ value: '0x0',
38
+ stateGas: '0x0',
39
+ },
40
+ {
41
+ mode: '0x2',
42
+ flags: '0x0',
43
+ data: '0x',
44
+ value: '0x0',
45
+ executionGas: '0xc350',
46
+ stateGas: '0x3e8',
47
+ },
48
+ ],
49
+ })
50
+ })
51
+ })
52
+
53
+ describe('TransactionRequest.fromRpc', () => {
54
+ test('decodes minimal requests without assigning gas limits', () => {
55
+ expect(
56
+ TransactionRequest.fromRpc({
57
+ type: '0x6',
58
+ frames: [
59
+ { mode: '0x2' },
60
+ { mode: '0x2', executionGas: '0x0', stateGas: '0x0', target: null },
61
+ ],
62
+ }),
63
+ ).toEqual({
64
+ type: 'eip8141',
65
+ frames: [
66
+ { mode: 2, flags: 0, data: '0x', value: 0n },
67
+ {
68
+ mode: 2,
69
+ flags: 0,
70
+ data: '0x',
71
+ value: 0n,
72
+ executionGas: 0n,
73
+ stateGas: 0n,
74
+ },
75
+ ],
76
+ })
77
+ })
78
+ })
79
+
80
+ describe('TxEnvelopeEip8141.toRpc', () => {
81
+ test('retains complete frame gas fields', () => {
82
+ const envelope = TxEnvelopeEip8141.toRpc(
83
+ TxEnvelopeEip8141.from({
84
+ chainId: 1,
85
+ frames: [Frame.from({ mode: 'sender' })],
86
+ sender: '0x1111111111111111111111111111111111111111',
87
+ }),
88
+ )
89
+ expect(envelope.frames).toEqual([
90
+ {
91
+ mode: '0x2',
92
+ flags: '0x0',
93
+ data: '0x',
94
+ value: '0x0',
95
+ executionGas: '0x0',
96
+ stateGas: '0x0',
97
+ },
98
+ ])
99
+ })
100
+ })
101
+
102
+ describe('FrameSignature.fromRpc', () => {
103
+ test('accepts empty signers', () => {
104
+ expect(FrameSignature.fromRpc({ scheme: '0x1', signer: '0x' })).toEqual({
105
+ scheme: 'secp256k1',
106
+ payload: '0x',
107
+ })
108
+ })
109
+ })
110
+
111
+ describe('TransactionEnvelope.toTransactionRequest', () => {
112
+ test('retains committed zero defaults when converting envelopes to requests', () => {
113
+ const envelope = TxEnvelopeEip8141.from({
114
+ chainId: 1,
115
+ frames: [Frame.from({ mode: 'sender', executionGas: 50_000n })],
116
+ sender: '0x1111111111111111111111111111111111111111',
117
+ signatures: [FrameSignature.from('0xaabb')],
118
+ })
119
+ const request = TransactionEnvelope.toTransactionRequest(envelope)
120
+ expect(request.frames).toEqual([
121
+ { mode: 'sender', executionGas: 50_000n, stateGas: 0n },
122
+ ])
123
+ expect(
124
+ TxEnvelopeEip8141.getSignPayload(
125
+ TransactionRequest.toEnvelope(
126
+ request,
127
+ ) as TxEnvelopeEip8141.TxEnvelopeEip8141,
128
+ ),
129
+ ).toBe(TxEnvelopeEip8141.getSignPayload(envelope))
130
+ })
131
+ })
@@ -91,3 +91,23 @@ test('from preserves structured signature types', () => {
91
91
  signature: { r: '0x01', s: '0x02' },
92
92
  })
93
93
  })
94
+
95
+ test('preserves protocol hex signature types', () => {
96
+ const signature = '0x1234' as const
97
+ const secp = FrameSignature.from({ scheme: 'secp256k1', signature })
98
+ expectTypeOf(secp.signature).toEqualTypeOf<typeof signature>()
99
+ const p256 = FrameSignature.from({
100
+ publicKey: { prefix: 4, x: '0x01', y: '0x02' },
101
+ scheme: 'p256',
102
+ signature,
103
+ })
104
+ expectTypeOf(p256.signature).toEqualTypeOf<typeof signature>()
105
+ // @ts-expect-error P256 hex signatures also require a public key.
106
+ FrameSignature.from({ scheme: 'p256', signature })
107
+ })
108
+
109
+ test('preserves empty protocol signature placeholders', () => {
110
+ const entry = FrameSignature.from({ scheme: 'p256', signature: '0x' })
111
+ expectTypeOf(entry.signature).toEqualTypeOf<'0x'>()
112
+ expectTypeOf(entry).toExtend<FrameSignature.P256>()
113
+ })
@@ -1,6 +1,6 @@
1
1
  import { p256 } from '@noble/curves/nist.js'
2
2
  import { secp256k1 } from '@noble/curves/secp256k1.js'
3
- import { FrameSignature, Hex, P256, Rlp, Secp256k1 } from 'ox'
3
+ import { FrameSignature, Hex, P256, Rlp, Secp256k1, Signature } from 'ox'
4
4
  import { describe, expect, test } from 'vite-plus/test'
5
5
 
6
6
  const arbitrary = {
@@ -21,6 +21,27 @@ const p256Bytes =
21
21
  '0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000026b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5'
22
22
 
23
23
  describe('from', () => {
24
+ test.each([
25
+ { scheme: 'secp256k1', signature: '0x' },
26
+ { scheme: 1, signature: '0x' },
27
+ { scheme: 'p256', signature: '0x' },
28
+ { scheme: 2, signature: '0x' },
29
+ ] as const)('accepts empty unsigned signatures: $scheme', (input) => {
30
+ const entry = FrameSignature.from(input)
31
+ const { signature: _, ...unsigned } = input
32
+ expect(entry).toEqual({ payload: '0x', ...input })
33
+ expect(FrameSignature.toTuple(entry)).toEqual(
34
+ FrameSignature.toTuple(unsigned),
35
+ )
36
+ expect(FrameSignature.toRpc(entry)).toEqual(FrameSignature.toRpc(unsigned))
37
+ expect(FrameSignature.validate(entry)).toBe(true)
38
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(false)
39
+ expect(() => FrameSignature.assert(entry, { signed: true }))
40
+ .toThrowErrorMatchingInlineSnapshot(`[FrameSignature.InvalidError: Invalid frame signature.
41
+
42
+ Details: Signature is required.]`)
43
+ })
44
+
24
45
  test.each(['0x', '0xaabb'] as const)(
25
46
  'constructs an arbitrary signature from %s',
26
47
  (signature) => {
@@ -455,15 +476,19 @@ describe('fromRpc', () => {
455
476
  expect(
456
477
  FrameSignature.fromRpc({
457
478
  msg: '0x',
458
- scheme: 0,
479
+ scheme: '0x0',
459
480
  signature: '0xaabb',
460
- signer: null,
481
+ signer: '0x',
461
482
  }),
462
483
  ).toEqual(FrameSignature.from('0xaabb'))
463
484
  })
464
485
  test('secp256k1 signature', () => {
465
486
  expect(
466
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: secpBytes }),
487
+ FrameSignature.fromRpc({
488
+ msg: '0x',
489
+ scheme: '0x1',
490
+ signature: secpBytes,
491
+ }),
467
492
  ).toEqual(
468
493
  FrameSignature.from({
469
494
  scheme: 'secp256k1',
@@ -478,24 +503,24 @@ describe('fromRpc', () => {
478
503
  test('P-256 signature', () => {
479
504
  const entry = FrameSignature.fromRpc({
480
505
  msg: '0x',
481
- scheme: 2,
506
+ scheme: '0x2',
482
507
  signature: p256Bytes,
483
508
  })
484
509
  expect(entry).toMatchObject({ payload: '0x', publicKey, scheme: 'p256' })
485
510
  expect(FrameSignature.toRpc(entry)).toEqual({
486
511
  msg: '0x',
487
- scheme: 2,
512
+ scheme: '0x2',
488
513
  signature: p256Bytes,
489
514
  })
490
515
  })
491
516
  test('unsigned placeholder', () => {
492
- expect(
493
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x' }),
494
- ).toEqual(FrameSignature.from({ scheme: 'secp256k1' }))
517
+ expect(FrameSignature.fromRpc({ scheme: '0x1' })).toEqual(
518
+ FrameSignature.from({ scheme: 'secp256k1' }),
519
+ )
495
520
  })
496
521
  test('malformed protocol signature', () => {
497
522
  expect(() =>
498
- FrameSignature.fromRpc({ msg: '0x', scheme: 1, signature: '0x01' }),
523
+ FrameSignature.fromRpc({ msg: '0x', scheme: '0x1', signature: '0x01' }),
499
524
  ).toThrowErrorMatchingInlineSnapshot(
500
525
  `[FrameSignature.InvalidError: Invalid frame signature.
501
526
 
@@ -505,6 +530,11 @@ Details: Invalid protocol signature length.]`,
505
530
  })
506
531
 
507
532
  describe('toRpc', () => {
533
+ test('omits an unsigned protocol signature', () => {
534
+ expect(
535
+ FrameSignature.toRpc(FrameSignature.from({ scheme: 'secp256k1' })),
536
+ ).toEqual({ msg: '0x', scheme: '0x1' })
537
+ })
508
538
  test('preserves explicit payload and signer', () => {
509
539
  const entry = FrameSignature.from({
510
540
  payload,
@@ -514,7 +544,7 @@ describe('toRpc', () => {
514
544
  })
515
545
  expect(FrameSignature.toRpc(entry)).toEqual({
516
546
  msg: payload,
517
- scheme: 1,
547
+ scheme: '0x1',
518
548
  signature: secpBytes,
519
549
  signer: '0x1111111111111111111111111111111111111111',
520
550
  })
@@ -552,3 +582,94 @@ describe('structured validation', () => {
552
582
  expect(() => FrameSignature.from(entry)).toThrow()
553
583
  })
554
584
  })
585
+
586
+ describe('hex signatures', () => {
587
+ test.each(['secp256k1', 1] as const)('secp256k1 scheme %s', (scheme) => {
588
+ const signature = Secp256k1.sign({ payload, privateKey })
589
+ const hex = Signature.toHex(signature)
590
+ const entry = FrameSignature.from({ scheme, signature: hex })
591
+ const structured = FrameSignature.from({ scheme, signature })
592
+
593
+ expect(entry.signature).toBe(hex)
594
+ expect(FrameSignature.toRpc(entry)).toEqual(
595
+ FrameSignature.toRpc(structured),
596
+ )
597
+ expect(FrameSignature.toTuple(entry)).toEqual(
598
+ FrameSignature.toTuple(structured),
599
+ )
600
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(true)
601
+ })
602
+
603
+ test.each(['p256', 2] as const)('P256 scheme %s', (scheme) => {
604
+ const signature = P256.sign({ payload, privateKey })
605
+ const hex = Signature.toHex({ r: signature.r, s: signature.s })
606
+ const entry = FrameSignature.from({ publicKey, scheme, signature: hex })
607
+ const structured = FrameSignature.from({ publicKey, scheme, signature })
608
+
609
+ expect(entry.signature).toBe(hex)
610
+ expect(FrameSignature.toRpc(entry)).toEqual(
611
+ FrameSignature.toRpc(structured),
612
+ )
613
+ expect(FrameSignature.toTuple(entry)).toEqual(
614
+ FrameSignature.toTuple(structured),
615
+ )
616
+ expect(FrameSignature.validate(entry, { signed: true })).toBe(true)
617
+ })
618
+
619
+ test.each(['0x01', `0x${'00'.repeat(65)}`, `0x${'gg'.repeat(65)}`] as const)(
620
+ 'rejects invalid secp256k1 hex: %s',
621
+ (signature) => {
622
+ expect(() =>
623
+ FrameSignature.from({ scheme: 'secp256k1', signature }),
624
+ ).toThrow()
625
+ },
626
+ )
627
+
628
+ test('requires secp256k1 recovery parity', () => {
629
+ const signature = Signature.toHex({
630
+ r: Hex.fromNumber(1, { size: 32 }),
631
+ s: Hex.fromNumber(2, { size: 32 }),
632
+ })
633
+ expect(() =>
634
+ FrameSignature.from({ scheme: 'secp256k1', signature }),
635
+ ).toThrow()
636
+ })
637
+
638
+ test('requires a public key with P256 hex', () => {
639
+ const entry = {
640
+ scheme: 'p256',
641
+ signature: Signature.toHex({
642
+ r: Hex.fromNumber(1, { size: 32 }),
643
+ s: Hex.fromNumber(2, { size: 32 }),
644
+ }),
645
+ } as const
646
+ // @ts-expect-error P256 requires a public key.
647
+ expect(() => FrameSignature.from(entry)).toThrow(
648
+ 'P-256 signatures require a public key.',
649
+ )
650
+ })
651
+
652
+ test('normalizes P256 scalars on encoding', () => {
653
+ const signature = {
654
+ r: Hex.fromNumber(1, { size: 32 }),
655
+ s: Hex.fromNumber(p256.Point.Fn.ORDER - 1n),
656
+ } as const
657
+ const entry = FrameSignature.from({
658
+ publicKey,
659
+ scheme: 'p256',
660
+ signature: Signature.toHex(signature),
661
+ })
662
+ expect(FrameSignature.toTuple(entry)).toEqual(
663
+ FrameSignature.toTuple(
664
+ FrameSignature.from({ publicKey, scheme: 'p256', signature }),
665
+ ),
666
+ )
667
+ })
668
+ })
669
+
670
+ test('rejects malformed P256 hex bytes', () => {
671
+ const signature = `0x${'00'.repeat(64)}gg` as const
672
+ expect(
673
+ FrameSignature.validate({ publicKey, scheme: 'p256', signature }),
674
+ ).toBe(false)
675
+ })
@@ -214,3 +214,48 @@ test('eth_call accepts a 4-tuple with state + block overrides', () => {
214
214
  ]
215
215
  >().toExtend<Params>()
216
216
  })
217
+
218
+ test('frame fill responses do not require raw bytes or lookup metadata', () => {
219
+ type Filled = Extract<
220
+ RpcSchema.Eth,
221
+ { Request: { method: 'eth_fillTransaction' } }
222
+ >['ReturnType']
223
+ const filled = {
224
+ tx: {
225
+ type: '0x6',
226
+ chainId: '0x1',
227
+ nonce: '0x0',
228
+ from: '0x1111111111111111111111111111111111111111',
229
+ frames: [
230
+ {
231
+ mode: '0x2',
232
+ flags: '0x0',
233
+ executionGas: '0x10000',
234
+ stateGas: '0x0',
235
+ value: '0x0',
236
+ data: '0x',
237
+ },
238
+ ],
239
+ signatures: [{ scheme: '0x1' }],
240
+ maxFeePerGas: '0x2',
241
+ maxPriorityFeePerGas: '0x1',
242
+ maxFeePerBlobGas: '0x0',
243
+ blobVersionedHashes: [],
244
+ },
245
+ } as const satisfies Filled
246
+ expectTypeOf(filled).toExtend<Filled>()
247
+ })
248
+
249
+ test('frame simulation results expose payer and frame results', () => {
250
+ type Result = Extract<
251
+ RpcSchema.Eth,
252
+ { Request: { method: 'eth_simulateV1' } }
253
+ >['ReturnType'][number]
254
+ type Call = NonNullable<Result['calls']>[number]
255
+ expectTypeOf<Call['payer']>().toEqualTypeOf<`0x${string}` | undefined>()
256
+ type Frame = NonNullable<Call['frameResults']>[number]
257
+ expectTypeOf<Frame['status']>().toEqualTypeOf<'0x0' | '0x1' | '0x2'>()
258
+ expectTypeOf<Frame['executionGasUsed']>().toEqualTypeOf<`0x${string}`>()
259
+ expectTypeOf<Frame['stateGasUsed']>().toEqualTypeOf<`0x${string}`>()
260
+ expectTypeOf<Frame['returnData']>().toEqualTypeOf<`0x${string}`>()
261
+ })
@@ -773,6 +773,153 @@ Resources:
773
773
  `)
774
774
  })
775
775
 
776
+ test('behavior: "Resources:" in statement', () => {
777
+ const message = `example.com wants you to sign in with your Ethereum account:
778
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
779
+
780
+ See the Resources: list below.
781
+
782
+ URI: https://example.com/path
783
+ Version: 1
784
+ Chain ID: 1
785
+ Nonce: foobarbaz
786
+ Issued At: 2023-02-01T00:00:00.000Z
787
+ Resources:
788
+ - https://example.com/foo
789
+ - https://example.com/bar`
790
+ const parsed = Siwe.parseMessage(message)
791
+ expect(parsed).toMatchInlineSnapshot(`
792
+ {
793
+ "address": "0xA0Cf798816D4b9b9866b5330EEa46a18382f251e",
794
+ "chainId": 1,
795
+ "domain": "example.com",
796
+ "issuedAt": 2023-02-01T00:00:00.000Z,
797
+ "nonce": "foobarbaz",
798
+ "resources": [
799
+ "https://example.com/foo",
800
+ "https://example.com/bar",
801
+ ],
802
+ "statement": "See the Resources: list below.",
803
+ "uri": "https://example.com/path",
804
+ "version": "1",
805
+ }
806
+ `)
807
+ })
808
+
809
+ test('behavior: "Resources:" in statement without resources', () => {
810
+ const message = `example.com wants you to sign in with your Ethereum account:
811
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
812
+
813
+ See the Resources: page.
814
+
815
+ URI: https://example.com/path
816
+ Version: 1
817
+ Chain ID: 1
818
+ Nonce: foobarbaz
819
+ Issued At: 2023-02-01T00:00:00.000Z`
820
+ const parsed = Siwe.parseMessage(message)
821
+ expect(parsed.resources).toBeUndefined()
822
+ })
823
+
824
+ test('behavior: "Resources:" in uri', () => {
825
+ const message = `example.com wants you to sign in with your Ethereum account:
826
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
827
+
828
+
829
+ URI: https://example.com/Resources:path
830
+ Version: 1
831
+ Chain ID: 1
832
+ Nonce: foobarbaz
833
+ Issued At: 2023-02-01T00:00:00.000Z
834
+ Resources:
835
+ - https://example.com/foo`
836
+ const parsed = Siwe.parseMessage(message)
837
+ expect(parsed.resources).toMatchInlineSnapshot(`
838
+ [
839
+ "https://example.com/foo",
840
+ ]
841
+ `)
842
+ })
843
+
844
+ test('behavior: "Resources:" in requestId', () => {
845
+ const message = `example.com wants you to sign in with your Ethereum account:
846
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
847
+
848
+
849
+ URI: https://example.com/path
850
+ Version: 1
851
+ Chain ID: 1
852
+ Nonce: foobarbaz
853
+ Issued At: 2023-02-01T00:00:00.000Z
854
+ Request ID: Resources:123
855
+ Resources:
856
+ - https://example.com/foo`
857
+ const parsed = Siwe.parseMessage(message)
858
+ expect(parsed.resources).toMatchInlineSnapshot(`
859
+ [
860
+ "https://example.com/foo",
861
+ ]
862
+ `)
863
+ })
864
+
865
+ test('behavior: "Resources:" in a resource', () => {
866
+ const message = `example.com wants you to sign in with your Ethereum account:
867
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
868
+
869
+
870
+ URI: https://example.com/path
871
+ Version: 1
872
+ Chain ID: 1
873
+ Nonce: foobarbaz
874
+ Issued At: 2023-02-01T00:00:00.000Z
875
+ Resources:
876
+ - https://example.com/Resources:foo
877
+ - https://example.com/bar`
878
+ const parsed = Siwe.parseMessage(message)
879
+ expect(parsed.resources).toMatchInlineSnapshot(`
880
+ [
881
+ "https://example.com/Resources:foo",
882
+ "https://example.com/bar",
883
+ ]
884
+ `)
885
+ })
886
+
887
+ test('behavior: empty requestId with resources', () => {
888
+ // EIP-4361 allows an empty Request ID (`request-id = *pchar`).
889
+ const message = `example.com wants you to sign in with your Ethereum account:
890
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
891
+
892
+
893
+ URI: https://example.com/path
894
+ Version: 1
895
+ Chain ID: 1
896
+ Nonce: foobarbaz
897
+ Issued At: 2023-02-01T00:00:00.000Z
898
+ Request ID: \nResources:
899
+ - https://example.com/foo`
900
+ const parsed = Siwe.parseMessage(message)
901
+ expect(parsed.resources).toMatchInlineSnapshot(`
902
+ [
903
+ "https://example.com/foo",
904
+ ]
905
+ `)
906
+ })
907
+
908
+ test('behavior: empty resources', () => {
909
+ const message = Siwe.createMessage({
910
+ address: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e',
911
+ chainId: 1,
912
+ domain: 'example.com',
913
+ issuedAt: new Date('2023-02-01T00:00:00.000Z'),
914
+ nonce: 'foobarbaz',
915
+ resources: [],
916
+ statement: 'Resources:',
917
+ uri: 'https://example.com/path',
918
+ version: '1',
919
+ })
920
+ expect(Siwe.parseMessage(message).resources).toMatchInlineSnapshot('[]')
921
+ })
922
+
776
923
  test('behavior: no suffix', () => {
777
924
  const message = `https://example.com wants you to sign in with your Ethereum account:
778
925
  0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
@@ -10,6 +10,33 @@ import {
10
10
  import { describe, expectTypeOf, test } from 'vp/test'
11
11
 
12
12
  describe('getType', () => {
13
+ test('infers frame transactions from nonce keys', () => {
14
+ expectTypeOf(
15
+ TransactionEnvelope.getType({ nonceKeys: [1n] }),
16
+ ).toEqualTypeOf<'eip8141'>()
17
+ expectTypeOf(
18
+ TransactionEnvelope.getType({ nonceKeys: [0n] }),
19
+ ).toEqualTypeOf<'eip8141'>()
20
+ expectTypeOf(
21
+ TransactionEnvelope.getType({ nonceKeys: [] }),
22
+ ).toEqualTypeOf<'eip8141'>()
23
+ expectTypeOf(
24
+ TransactionEnvelope.getType({
25
+ nonceKeys: [1n],
26
+ frames: undefined,
27
+ authorizationList: [],
28
+ blobVersionedHashes: [],
29
+ gasPrice: 1n,
30
+ }),
31
+ ).toEqualTypeOf<'eip8141'>()
32
+ expectTypeOf(
33
+ TransactionEnvelope.getType({ nonceKeys: undefined, maxFeePerGas: 1n }),
34
+ ).toEqualTypeOf<'eip1559'>()
35
+ expectTypeOf(
36
+ TransactionEnvelope.getType({ nonceKeys: [1n], type: 'eip1559' }),
37
+ ).toEqualTypeOf<'eip1559'>()
38
+ })
39
+
13
40
  test('behavior: infers explicit transaction types', () => {
14
41
  expectTypeOf(
15
42
  TransactionEnvelope.getType({ type: 'legacy' }),
@@ -99,6 +99,32 @@ test('exports', () => {
99
99
  })
100
100
 
101
101
  describe('getType', () => {
102
+ test('infers frame transactions from nonce keys', () => {
103
+ expect([
104
+ TransactionEnvelope.getType({ nonceKeys: [1n] }),
105
+ TransactionEnvelope.getType({ nonceKeys: [0n] }),
106
+ TransactionEnvelope.getType({ nonceKeys: [] }),
107
+ TransactionEnvelope.getType({
108
+ nonceKeys: [1n],
109
+ frames: undefined,
110
+ authorizationList: [],
111
+ blobVersionedHashes: [],
112
+ gasPrice: 1n,
113
+ }),
114
+ TransactionEnvelope.getType({ nonceKeys: undefined, maxFeePerGas: 1n }),
115
+ TransactionEnvelope.getType({ nonceKeys: [1n], type: 'eip1559' }),
116
+ ]).toMatchInlineSnapshot(`
117
+ [
118
+ "eip8141",
119
+ "eip8141",
120
+ "eip8141",
121
+ "eip8141",
122
+ "eip1559",
123
+ "eip1559",
124
+ ]
125
+ `)
126
+ })
127
+
102
128
  test('frame fields take precedence over blobs and fees', () => {
103
129
  expect(
104
130
  TransactionEnvelope.getType({
@@ -434,7 +460,7 @@ describe('toTransactionRequest', () => {
434
460
  test('maps frame sender to request from', () => {
435
461
  expect(TransactionEnvelope.toTransactionRequest(eip8141)).toEqual({
436
462
  chainId: 1,
437
- frames: [{}],
463
+ frames: [{ executionGas: 0n, stateGas: 0n }],
438
464
  from: '0x1111111111111111111111111111111111111111',
439
465
  type: 'eip8141',
440
466
  })