ox 1.7.4 → 1.8.0

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 (237) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/core/AbiEvent.d.ts.map +1 -1
  3. package/dist/core/AbiEvent.js +10 -9
  4. package/dist/core/AbiEvent.js.map +1 -1
  5. package/dist/core/Blobs.d.ts.map +1 -1
  6. package/dist/core/Blobs.js +6 -2
  7. package/dist/core/Blobs.js.map +1 -1
  8. package/dist/core/Frame.d.ts +290 -0
  9. package/dist/core/Frame.d.ts.map +1 -0
  10. package/dist/core/Frame.js +306 -0
  11. package/dist/core/Frame.js.map +1 -0
  12. package/dist/core/FrameReceipt.d.ts +92 -0
  13. package/dist/core/FrameReceipt.d.ts.map +1 -0
  14. package/dist/core/FrameReceipt.js +72 -0
  15. package/dist/core/FrameReceipt.js.map +1 -0
  16. package/dist/core/FrameSignature.d.ts +357 -0
  17. package/dist/core/FrameSignature.d.ts.map +1 -0
  18. package/dist/core/FrameSignature.js +411 -0
  19. package/dist/core/FrameSignature.js.map +1 -0
  20. package/dist/core/Signature.d.ts +6 -5
  21. package/dist/core/Signature.d.ts.map +1 -1
  22. package/dist/core/Signature.js +17 -7
  23. package/dist/core/Signature.js.map +1 -1
  24. package/dist/core/Siwe.d.ts.map +1 -1
  25. package/dist/core/Siwe.js +21 -1
  26. package/dist/core/Siwe.js.map +1 -1
  27. package/dist/core/Transaction.d.ts +29 -4
  28. package/dist/core/Transaction.d.ts.map +1 -1
  29. package/dist/core/Transaction.js +25 -4
  30. package/dist/core/Transaction.js.map +1 -1
  31. package/dist/core/TransactionReceipt.d.ts +13 -4
  32. package/dist/core/TransactionReceipt.d.ts.map +1 -1
  33. package/dist/core/TransactionReceipt.js +14 -0
  34. package/dist/core/TransactionReceipt.js.map +1 -1
  35. package/dist/core/TransactionRequest.d.ts +15 -7
  36. package/dist/core/TransactionRequest.d.ts.map +1 -1
  37. package/dist/core/TransactionRequest.js +47 -1
  38. package/dist/core/TransactionRequest.js.map +1 -1
  39. package/dist/core/TxEnvelope.d.ts +35 -21
  40. package/dist/core/TxEnvelope.d.ts.map +1 -1
  41. package/dist/core/TxEnvelope.js +46 -6
  42. package/dist/core/TxEnvelope.js.map +1 -1
  43. package/dist/core/TxEnvelopeEip8141.d.ts +341 -0
  44. package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -0
  45. package/dist/core/TxEnvelopeEip8141.js +485 -0
  46. package/dist/core/TxEnvelopeEip8141.js.map +1 -0
  47. package/dist/core/internal/curves.d.ts +2 -0
  48. package/dist/core/internal/curves.d.ts.map +1 -1
  49. package/dist/core/internal/curves.js +2 -0
  50. package/dist/core/internal/curves.js.map +1 -1
  51. package/dist/index.d.ts +73 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +73 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/tempo/KeyAuthorization.d.ts +42 -38
  56. package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
  57. package/dist/tempo/KeyAuthorization.js +24 -4
  58. package/dist/tempo/KeyAuthorization.js.map +1 -1
  59. package/dist/tempo/MultisigConfig.d.ts +18 -15
  60. package/dist/tempo/MultisigConfig.d.ts.map +1 -1
  61. package/dist/tempo/MultisigConfig.js +49 -20
  62. package/dist/tempo/MultisigConfig.js.map +1 -1
  63. package/dist/tempo/MultisigOperation.d.ts +3 -2
  64. package/dist/tempo/MultisigOperation.d.ts.map +1 -1
  65. package/dist/tempo/MultisigOperation.js +20 -100
  66. package/dist/tempo/MultisigOperation.js.map +1 -1
  67. package/dist/tempo/MultisigSimulation.d.ts +2 -49
  68. package/dist/tempo/MultisigSimulation.d.ts.map +1 -1
  69. package/dist/tempo/MultisigSimulation.js +18 -46
  70. package/dist/tempo/MultisigSimulation.js.map +1 -1
  71. package/dist/tempo/SignatureEnvelope.d.ts +72 -140
  72. package/dist/tempo/SignatureEnvelope.d.ts.map +1 -1
  73. package/dist/tempo/SignatureEnvelope.js +130 -181
  74. package/dist/tempo/SignatureEnvelope.js.map +1 -1
  75. package/dist/tempo/Transaction.d.ts +2 -0
  76. package/dist/tempo/Transaction.d.ts.map +1 -1
  77. package/dist/tempo/TransactionReceipt.d.ts +2 -0
  78. package/dist/tempo/TransactionReceipt.d.ts.map +1 -1
  79. package/dist/tempo/TransactionRequest.d.ts +4 -2
  80. package/dist/tempo/TransactionRequest.d.ts.map +1 -1
  81. package/dist/tempo/TransactionRequest.js +6 -1
  82. package/dist/tempo/TransactionRequest.js.map +1 -1
  83. package/dist/tempo/index.d.ts +3 -1
  84. package/dist/tempo/index.d.ts.map +1 -1
  85. package/dist/tempo/index.js +3 -1
  86. package/dist/tempo/index.js.map +1 -1
  87. package/dist/zod/Block.d.ts +123 -0
  88. package/dist/zod/Block.d.ts.map +1 -1
  89. package/dist/zod/Frame.d.ts +73 -0
  90. package/dist/zod/Frame.d.ts.map +1 -0
  91. package/dist/zod/Frame.js +74 -0
  92. package/dist/zod/Frame.js.map +1 -0
  93. package/dist/zod/FrameReceipt.d.ts +50 -0
  94. package/dist/zod/FrameReceipt.d.ts.map +1 -0
  95. package/dist/zod/FrameReceipt.js +37 -0
  96. package/dist/zod/FrameReceipt.js.map +1 -0
  97. package/dist/zod/FrameSignature.d.ts +63 -0
  98. package/dist/zod/FrameSignature.d.ts.map +1 -0
  99. package/dist/zod/FrameSignature.js +66 -0
  100. package/dist/zod/FrameSignature.js.map +1 -0
  101. package/dist/zod/RpcSchema.d.ts +806 -11
  102. package/dist/zod/RpcSchema.d.ts.map +1 -1
  103. package/dist/zod/Transaction.d.ts +252 -0
  104. package/dist/zod/Transaction.d.ts.map +1 -1
  105. package/dist/zod/Transaction.js +54 -0
  106. package/dist/zod/Transaction.js.map +1 -1
  107. package/dist/zod/TransactionEnvelope.d.ts +233 -4
  108. package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
  109. package/dist/zod/TransactionEnvelope.js +9 -0
  110. package/dist/zod/TransactionEnvelope.js.map +1 -1
  111. package/dist/zod/TransactionReceipt.d.ts +48 -0
  112. package/dist/zod/TransactionReceipt.d.ts.map +1 -1
  113. package/dist/zod/TransactionReceipt.js +8 -3
  114. package/dist/zod/TransactionReceipt.js.map +1 -1
  115. package/dist/zod/TransactionRequest.d.ts +50 -2
  116. package/dist/zod/TransactionRequest.d.ts.map +1 -1
  117. package/dist/zod/TransactionRequest.js +10 -4
  118. package/dist/zod/TransactionRequest.js.map +1 -1
  119. package/dist/zod/TxEnvelopeEip8141.d.ts +166 -0
  120. package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -0
  121. package/dist/zod/TxEnvelopeEip8141.js +76 -0
  122. package/dist/zod/TxEnvelopeEip8141.js.map +1 -0
  123. package/dist/zod/internal/Frame.d.ts +4 -0
  124. package/dist/zod/internal/Frame.d.ts.map +1 -0
  125. package/dist/zod/internal/Frame.js +17 -0
  126. package/dist/zod/internal/Frame.js.map +1 -0
  127. package/dist/zod/internal/rpcSchemas/Eth.d.ts +842 -11
  128. package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
  129. package/dist/zod/internal/rpcSchemas/Wallet.d.ts +56 -2
  130. package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
  131. package/dist/zod/tempo/AuthorizationTempo.d.ts +350 -10
  132. package/dist/zod/tempo/AuthorizationTempo.d.ts.map +1 -1
  133. package/dist/zod/tempo/KeyAuthorization.d.ts +242 -422
  134. package/dist/zod/tempo/KeyAuthorization.d.ts.map +1 -1
  135. package/dist/zod/tempo/KeyAuthorization.js +29 -31
  136. package/dist/zod/tempo/KeyAuthorization.js.map +1 -1
  137. package/dist/zod/tempo/MultisigSimulation.d.ts +8 -76
  138. package/dist/zod/tempo/MultisigSimulation.d.ts.map +1 -1
  139. package/dist/zod/tempo/MultisigSimulation.js +3 -37
  140. package/dist/zod/tempo/MultisigSimulation.js.map +1 -1
  141. package/dist/zod/tempo/RpcSchemaTempo.d.ts +424 -228
  142. package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
  143. package/dist/zod/tempo/SignatureEnvelope.d.ts +105 -3
  144. package/dist/zod/tempo/SignatureEnvelope.d.ts.map +1 -1
  145. package/dist/zod/tempo/SignatureEnvelope.js +21 -9
  146. package/dist/zod/tempo/SignatureEnvelope.js.map +1 -1
  147. package/dist/zod/tempo/Transaction.d.ts +783 -432
  148. package/dist/zod/tempo/Transaction.d.ts.map +1 -1
  149. package/dist/zod/tempo/TransactionRequest.d.ts +590 -473
  150. package/dist/zod/tempo/TransactionRequest.d.ts.map +1 -1
  151. package/dist/zod/tempo/TransactionRequest.js +8 -0
  152. package/dist/zod/tempo/TransactionRequest.js.map +1 -1
  153. package/dist/zod/tempo/TxEnvelopeTempo.d.ts +327 -213
  154. package/dist/zod/tempo/TxEnvelopeTempo.d.ts.map +1 -1
  155. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts +105 -3
  156. package/dist/zod/tempo/ZoneRpcAuthentication.d.ts.map +1 -1
  157. package/dist/zod/z.d.ts +4 -0
  158. package/dist/zod/z.d.ts.map +1 -1
  159. package/dist/zod/z.js +4 -0
  160. package/dist/zod/z.js.map +1 -1
  161. package/package.json +41 -1
  162. package/src/core/AbiEvent.ts +11 -9
  163. package/src/core/Blobs.ts +6 -2
  164. package/src/core/Frame.ts +443 -0
  165. package/src/core/FrameReceipt.ts +117 -0
  166. package/src/core/FrameSignature.ts +593 -0
  167. package/src/core/Signature.ts +41 -11
  168. package/src/core/Siwe.ts +20 -1
  169. package/src/core/Transaction.ts +82 -5
  170. package/src/core/TransactionReceipt.ts +41 -3
  171. package/src/core/TransactionRequest.ts +71 -4
  172. package/src/core/TxEnvelope.ts +159 -90
  173. package/src/core/TxEnvelopeEip8141.ts +715 -0
  174. package/src/core/_test/AbiEvent.test.ts +45 -0
  175. package/src/core/_test/Blobs.test.ts +22 -0
  176. package/src/core/_test/Frame.test-d.ts +96 -0
  177. package/src/core/_test/Frame.test.ts +267 -0
  178. package/src/core/_test/FrameReceipt.test.ts +86 -0
  179. package/src/core/_test/FrameSignature.test-d.ts +93 -0
  180. package/src/core/_test/FrameSignature.test.ts +554 -0
  181. package/src/core/_test/Signature.test.ts +166 -0
  182. package/src/core/_test/Siwe.test.ts +100 -1
  183. package/src/core/_test/Transaction.test.ts +33 -0
  184. package/src/core/_test/TransactionEnvelope.test-d.ts +74 -0
  185. package/src/core/_test/TransactionEnvelope.test.ts +107 -0
  186. package/src/core/_test/TransactionReceipt.test.ts +59 -0
  187. package/src/core/_test/TransactionRequest.test-d.ts +50 -0
  188. package/src/core/_test/TransactionRequest.test.ts +88 -1
  189. package/src/core/_test/TxEnvelopeEip8141.test-d.ts +44 -0
  190. package/src/core/_test/TxEnvelopeEip8141.test.ts +1775 -0
  191. package/src/core/_test/curves.test.ts +6 -1
  192. package/src/core/_test/index.test.ts +4 -0
  193. package/src/core/internal/curves.ts +4 -0
  194. package/src/index.ts +77 -0
  195. package/src/tempo/AuthorizationTempo.test.ts +8 -4
  196. package/src/tempo/KeyAuthorization.test-d.ts +16 -63
  197. package/src/tempo/KeyAuthorization.test.ts +69 -11
  198. package/src/tempo/KeyAuthorization.ts +107 -63
  199. package/src/tempo/MultisigConfig.test.ts +44 -20
  200. package/src/tempo/MultisigConfig.ts +65 -23
  201. package/src/tempo/MultisigOperation.test.ts +79 -550
  202. package/src/tempo/MultisigOperation.ts +31 -146
  203. package/src/tempo/MultisigSimulation.test-d.ts +2 -2
  204. package/src/tempo/MultisigSimulation.test.ts +21 -110
  205. package/src/tempo/MultisigSimulation.ts +25 -99
  206. package/src/tempo/SignatureEnvelope.test-d.ts +29 -47
  207. package/src/tempo/SignatureEnvelope.test.ts +328 -541
  208. package/src/tempo/SignatureEnvelope.ts +196 -291
  209. package/src/tempo/Transaction.test.ts +4 -4
  210. package/src/tempo/TransactionRequest.test.ts +17 -80
  211. package/src/tempo/TransactionRequest.ts +19 -2
  212. package/src/tempo/index.ts +3 -1
  213. package/src/tempo/multisig.e2e.test.ts +321 -860
  214. package/src/version.ts +1 -1
  215. package/src/zod/Frame.ts +82 -0
  216. package/src/zod/FrameReceipt.ts +48 -0
  217. package/src/zod/FrameSignature.ts +78 -0
  218. package/src/zod/Transaction.ts +63 -0
  219. package/src/zod/TransactionEnvelope.ts +33 -0
  220. package/src/zod/TransactionReceipt.ts +14 -3
  221. package/src/zod/TransactionRequest.ts +18 -3
  222. package/src/zod/TxEnvelopeEip8141.ts +102 -0
  223. package/src/zod/_test/Frame.test-d.ts +41 -0
  224. package/src/zod/_test/Frame.test.ts +54 -0
  225. package/src/zod/_test/FrameReceipt.test.ts +75 -0
  226. package/src/zod/_test/FrameSignature.test.ts +94 -0
  227. package/src/zod/_test/TxEnvelopeEip8141.test.ts +116 -0
  228. package/src/zod/internal/Frame.ts +26 -0
  229. package/src/zod/tempo/KeyAuthorization.ts +50 -39
  230. package/src/zod/tempo/MultisigSimulation.ts +3 -48
  231. package/src/zod/tempo/SignatureEnvelope.ts +46 -35
  232. package/src/zod/tempo/TransactionRequest.ts +14 -0
  233. package/src/zod/tempo/_test/KeyAuthorization.test.ts +69 -20
  234. package/src/zod/tempo/_test/MultisigSimulation.test.ts +6 -31
  235. package/src/zod/tempo/_test/SignatureEnvelope.test.ts +118 -85
  236. package/src/zod/tempo/_test/TransactionRequest.test.ts +9 -19
  237. package/src/zod/z.ts +4 -0
@@ -572,6 +572,172 @@ describe('toBytes', () => {
572
572
  })
573
573
  })
574
574
 
575
+ describe('toCompactBytes', () => {
576
+ test('default', () => {
577
+ const bytes = Signature.toCompactBytes({
578
+ r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
579
+ s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
580
+ yParity: 1,
581
+ })
582
+ expect(bytes.length).toBe(64)
583
+ expect(Hex.fromBytes(bytes)).toBe(
584
+ '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
585
+ )
586
+ })
587
+
588
+ test('behavior: r/s shorter than 32 bytes are left-padded, not right-padded', () => {
589
+ const bytes = Signature.toCompactBytes({
590
+ r: '0x01',
591
+ s: '0x02',
592
+ yParity: 0,
593
+ })
594
+ // `r` occupies the first 32 bytes and must be the big-endian integer `1`
595
+ // (left-padded), not `1` shifted into the top byte (right-padded).
596
+ expect(Hex.fromBytes(bytes.subarray(0, 32))).toBe(
597
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
598
+ )
599
+ expect(Hex.fromBytes(bytes.subarray(32, 64))).toBe(
600
+ '0x0000000000000000000000000000000000000000000000000000000000000002',
601
+ )
602
+ })
603
+
604
+ test('behavior: round-trips through recoverPublicKey for a signature whose r has a leading zero byte', async () => {
605
+ const { Secp256k1 } = await import('ox')
606
+ const payload = `0x${'11'.repeat(32)}` as const
607
+ const privateKey = Hex.fromNumber(4n * 123456789013n + 7n, { size: 32 })
608
+ const signature = Secp256k1.sign({ payload, privateKey })
609
+ // sanity: this fixture's `r` genuinely has a leading zero byte
610
+ expect(signature.r.startsWith('0x00')).toBe(true)
611
+ const expectedPublicKey = Secp256k1.recoverPublicKey({ payload, signature })
612
+
613
+ // simulate an external source (RPC/subgraph/DB) that stores `r` in
614
+ // minimal (non-zero-padded) form, as the `Hex.Hex` type permits
615
+ const minimalR = `0x${signature.r.slice(4)}` as const
616
+ const recoveredBytes = Signature.toRecoveredBytes({
617
+ r: minimalR,
618
+ s: signature.s,
619
+ yParity: signature.yParity,
620
+ })
621
+ const roundTripped = Signature.fromRecoveredBytes(recoveredBytes)
622
+ const recoveredPublicKey = Secp256k1.recoverPublicKey({
623
+ payload,
624
+ signature: roundTripped,
625
+ })
626
+ expect(recoveredPublicKey.x).toBe(expectedPublicKey.x)
627
+ expect(recoveredPublicKey.y).toBe(expectedPublicKey.y)
628
+ })
629
+ })
630
+
631
+ describe('fromCompactBytes', () => {
632
+ test('default', () => {
633
+ const signature = Signature.fromCompactBytes(
634
+ Bytes.fromHex(
635
+ '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
636
+ ),
637
+ )
638
+ expect(signature).toStrictEqual({
639
+ r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
640
+ s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
641
+ })
642
+ })
643
+
644
+ test('behavior: throws on a byte length other than 64', () => {
645
+ expect(() => Signature.fromCompactBytes(new Uint8Array(65)))
646
+ .toThrowErrorMatchingInlineSnapshot(`
647
+ [Signature.InvalidSerializedSizeError: Value \`0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\` is an invalid signature size.
648
+
649
+ Expected: 64 bytes.
650
+ Received 65 bytes.]
651
+ `)
652
+ })
653
+ })
654
+
655
+ describe('toRecoveredBytes', () => {
656
+ test('default', () => {
657
+ const bytes = Signature.toRecoveredBytes({
658
+ r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
659
+ s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
660
+ yParity: 1,
661
+ })
662
+ expect(bytes.length).toBe(65)
663
+ expect(bytes[0]).toBe(1)
664
+ expect(Hex.fromBytes(bytes.subarray(1))).toBe(
665
+ '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
666
+ )
667
+ })
668
+
669
+ test('behavior: r/s shorter than 32 bytes are left-padded, not right-padded', () => {
670
+ const bytes = Signature.toRecoveredBytes({
671
+ r: '0x01',
672
+ s: '0x02',
673
+ yParity: 0,
674
+ })
675
+ expect(Hex.fromBytes(bytes.subarray(1, 33))).toBe(
676
+ '0x0000000000000000000000000000000000000000000000000000000000000001',
677
+ )
678
+ expect(Hex.fromBytes(bytes.subarray(33, 65))).toBe(
679
+ '0x0000000000000000000000000000000000000000000000000000000000000002',
680
+ )
681
+ })
682
+
683
+ test('behavior: throws before coercing an out-of-range yParity', () => {
684
+ expect(() =>
685
+ Signature.toRecoveredBytes({
686
+ r: '0x01',
687
+ s: '0x02',
688
+ yParity: 256,
689
+ }),
690
+ ).toThrowErrorMatchingInlineSnapshot(
691
+ `[Signature.InvalidYParityError: Value \`256\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
692
+ )
693
+ })
694
+ })
695
+
696
+ describe('fromRecoveredBytes', () => {
697
+ test('default', () => {
698
+ const bytes = new Uint8Array(65)
699
+ bytes[0] = 1
700
+ bytes.set(
701
+ Bytes.fromHex(
702
+ '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
703
+ ),
704
+ 1,
705
+ )
706
+ bytes.set(
707
+ Bytes.fromHex(
708
+ '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
709
+ ),
710
+ 33,
711
+ )
712
+ const signature = Signature.fromRecoveredBytes(bytes)
713
+ expect(signature).toStrictEqual({
714
+ r: '0x6e100a352ec6ad1b70802290e18aeed190704973570f3b8ed42cb9808e2ea6bf',
715
+ s: '0x4a90a229a244495b41890987806fcbd2d5d23fc0dbe5f5256c2613c039d76db8',
716
+ yParity: 1,
717
+ })
718
+ })
719
+
720
+ test('behavior: throws on a byte length other than 65', () => {
721
+ expect(() => Signature.fromRecoveredBytes(new Uint8Array(64)))
722
+ .toThrowErrorMatchingInlineSnapshot(`
723
+ [Signature.InvalidSerializedSizeError: Value \`0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\` is an invalid signature size.
724
+
725
+ Expected: 65 bytes.
726
+ Received 64 bytes.]
727
+ `)
728
+ })
729
+
730
+ test('behavior: throws on an out-of-range yParity byte', () => {
731
+ const bytes = new Uint8Array(65)
732
+ bytes[0] = 7
733
+ expect(() =>
734
+ Signature.fromRecoveredBytes(bytes),
735
+ ).toThrowErrorMatchingInlineSnapshot(
736
+ `[Signature.InvalidYParityError: Value \`7\` is an invalid y-parity value. Y-parity must be 0 or 1.]`,
737
+ )
738
+ })
739
+ })
740
+
575
741
  describe('toDerHex', () => {
576
742
  test('default', () => {
577
743
  const signature = Signature.from({
@@ -324,6 +324,53 @@ describe('createMessage', () => {
324
324
  `)
325
325
  })
326
326
 
327
+ test('behavior: uri with no path (authority only)', () => {
328
+ vi.useFakeTimers()
329
+ vi.setSystemTime(new Date(Date.UTC(2023, 1, 1)))
330
+
331
+ expect(Siwe.createMessage({ ...message, uri: 'https://example.com' }))
332
+ .toMatchInlineSnapshot(`
333
+ "example.com wants you to sign in with your Ethereum account:
334
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
335
+
336
+
337
+ URI: https://example.com
338
+ Version: 1
339
+ Chain ID: 1
340
+ Nonce: foobarbaz
341
+ Issued At: 2023-02-01T00:00:00.000Z"
342
+ `)
343
+
344
+ vi.useRealTimers()
345
+ })
346
+
347
+ test('behavior: resources entry with no path (authority only)', () => {
348
+ vi.useFakeTimers()
349
+ vi.setSystemTime(new Date(Date.UTC(2023, 1, 1)))
350
+
351
+ expect(
352
+ Siwe.createMessage({
353
+ ...message,
354
+ resources: ['https://example.com', 'https://example.com/foo'],
355
+ }),
356
+ ).toMatchInlineSnapshot(`
357
+ "example.com wants you to sign in with your Ethereum account:
358
+ 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e
359
+
360
+
361
+ URI: https://example.com/path
362
+ Version: 1
363
+ Chain ID: 1
364
+ Nonce: foobarbaz
365
+ Issued At: 2023-02-01T00:00:00.000Z
366
+ Resources:
367
+ - https://example.com
368
+ - https://example.com/foo"
369
+ `)
370
+
371
+ vi.useRealTimers()
372
+ })
373
+
327
374
  test('behavior: invalid version', () => {
328
375
  expect(() =>
329
376
  // @ts-expect-error
@@ -373,7 +420,7 @@ describe('createMessage', () => {
373
420
  - Every resource must be a RFC 3986 URI.
374
421
  - See https://www.rfc-editor.org/rfc/rfc3986
375
422
 
376
- Provided value: https://example.com]
423
+ Provided value: foo]
377
424
  `)
378
425
  })
379
426
 
@@ -426,6 +473,58 @@ describe('isUri', () => {
426
473
  )
427
474
  })
428
475
 
476
+ test('behavior: authority with empty path (bare origin)', () => {
477
+ expect(Siwe.isUri('https://example.com')).toMatchInlineSnapshot(
478
+ `"https://example.com"`,
479
+ )
480
+ })
481
+
482
+ test('behavior: authority with empty path and port', () => {
483
+ expect(Siwe.isUri('https://example.com:8080')).toMatchInlineSnapshot(
484
+ `"https://example.com:8080"`,
485
+ )
486
+ })
487
+
488
+ test('behavior: authority with empty path and query', () => {
489
+ expect(Siwe.isUri('https://example.com?a=1')).toMatchInlineSnapshot(
490
+ `"https://example.com?a=1"`,
491
+ )
492
+ })
493
+
494
+ test('behavior: authority with empty path and fragment', () => {
495
+ expect(Siwe.isUri('https://example.com#top')).toMatchInlineSnapshot(
496
+ `"https://example.com#top"`,
497
+ )
498
+ })
499
+
500
+ test('behavior: authority with empty path (non-http scheme)', () => {
501
+ expect(Siwe.isUri('ipfs://bafybeiabc')).toMatchInlineSnapshot(
502
+ `"ipfs://bafybeiabc"`,
503
+ )
504
+ })
505
+
506
+ test('behavior: authority variants with empty path', () => {
507
+ expect([
508
+ Siwe.isUri('https://[2001:db8::1]'),
509
+ Siwe.isUri('https://user:pass@example.com:8080'),
510
+ Siwe.isUri('foo://[v1.fe]'),
511
+ ]).toMatchInlineSnapshot(`
512
+ [
513
+ "https://[2001:db8::1]",
514
+ "https://user:pass@example.com:8080",
515
+ "foo://[v1.fe]",
516
+ ]
517
+ `)
518
+ })
519
+
520
+ test.each([
521
+ 'https://example.com:bad',
522
+ 'https://[:::]',
523
+ 'https://user@@example.com',
524
+ ])('behavior: invalid authority with empty path `%s`', (uri) => {
525
+ expect(Siwe.isUri(uri)).toMatchInlineSnapshot(`false`)
526
+ })
527
+
429
528
  test('behavior: check for illegal characters', () => {
430
529
  expect(Siwe.isUri('^')).toBeFalsy()
431
530
  })
@@ -3,6 +3,9 @@ import {
3
3
  Secp256k1,
4
4
  Transaction,
5
5
  TxEnvelopeEip1559,
6
+ Frame,
7
+ FrameSignature,
8
+ TxEnvelopeEip8141,
6
9
  TxEnvelopeLegacy,
7
10
  } from 'ox'
8
11
  import { describe, expect, test } from 'vp/test'
@@ -2046,3 +2049,33 @@ test('exports', () => {
2046
2049
  ]
2047
2050
  `)
2048
2051
  })
2052
+
2053
+ describe('frame transaction RPC', () => {
2054
+ test('preserves pending metadata and large chain IDs without outer signatures', () => {
2055
+ const envelope = TxEnvelopeEip8141.from({
2056
+ chainId: 9007199254740993n,
2057
+ frames: [Frame.from({})],
2058
+ sender: '0x1111111111111111111111111111111111111111',
2059
+ signatures: [FrameSignature.from({ scheme: 'secp256k1' })],
2060
+ })
2061
+ const rpc = {
2062
+ ...TxEnvelopeEip8141.toRpc(envelope),
2063
+ blockHash: null,
2064
+ blockNumber: null,
2065
+ blockTimestamp: null,
2066
+ gas: '0x0',
2067
+ hash: '0x00',
2068
+ input: '0x',
2069
+ to: null,
2070
+ transactionIndex: null,
2071
+ value: '0x0',
2072
+ } satisfies Transaction.Eip8141Rpc<true>
2073
+ const transaction = Transaction.fromRpc(rpc, { pending: true })!
2074
+ expect(transaction.chainId).toBe(9007199254740993n)
2075
+ expect(transaction.blockNumber).toBeNull()
2076
+ expect(transaction.transactionIndex).toBeNull()
2077
+ expect(transaction).not.toHaveProperty('r')
2078
+ expect(transaction).not.toHaveProperty('s')
2079
+ expect(Transaction.toRpc(transaction, { pending: true })).toEqual(rpc)
2080
+ })
2081
+ })
@@ -4,6 +4,7 @@ import {
4
4
  type TxEnvelopeEip2930,
5
5
  type TxEnvelopeEip4844,
6
6
  type TxEnvelopeEip7702,
7
+ type TxEnvelopeEip8141,
7
8
  type TxEnvelopeLegacy,
8
9
  } from 'ox'
9
10
  import { describe, expectTypeOf, test } from 'vp/test'
@@ -180,3 +181,76 @@ describe('toRpc', () => {
180
181
  expectTypeOf(rpc).toEqualTypeOf<TxEnvelopeEip1559.Rpc>()
181
182
  })
182
183
  })
184
+
185
+ describe('eip8141', () => {
186
+ test('preserves constructor literals', () => {
187
+ const envelope = TransactionEnvelope.from({
188
+ chainId: 1n,
189
+ frames: [],
190
+ sender: '0x1111111111111111111111111111111111111111',
191
+ })
192
+ expectTypeOf(envelope).toEqualTypeOf<{
193
+ readonly chainId: 1n
194
+ readonly frames: readonly []
195
+ readonly sender: '0x1111111111111111111111111111111111111111'
196
+ readonly type: 'eip8141'
197
+ }>()
198
+ expectTypeOf(
199
+ TransactionEnvelope.serialize(envelope),
200
+ ).toEqualTypeOf<TxEnvelopeEip8141.Serialized>()
201
+ expectTypeOf(
202
+ TransactionEnvelope.deserialize('0x06c0'),
203
+ ).toEqualTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
204
+ expectTypeOf(
205
+ TransactionEnvelope.from('0x06c0'),
206
+ ).toEqualTypeOf<TxEnvelopeEip8141.TxEnvelopeEip8141>()
207
+ expectTypeOf(
208
+ TransactionEnvelope.getSerializedType('0x06c0'),
209
+ ).toEqualTypeOf<'eip8141'>()
210
+ expectTypeOf(envelope).toMatchTypeOf<TransactionEnvelope.TxEnvelope<true>>()
211
+ expectTypeOf(
212
+ TransactionEnvelope.toRpc(envelope),
213
+ ).toEqualTypeOf<TxEnvelopeEip8141.Rpc>()
214
+ })
215
+
216
+ test('frames take precedence over blob and fee fields', () => {
217
+ expectTypeOf(
218
+ TransactionEnvelope.getType({
219
+ blobVersionedHashes: [],
220
+ frames: [],
221
+ maxFeePerGas: 1n,
222
+ }),
223
+ ).toEqualTypeOf<'eip8141'>()
224
+ expectTypeOf(
225
+ TransactionEnvelope.getType({ frames: undefined, maxFeePerGas: 1n }),
226
+ ).toEqualTypeOf<'eip1559'>()
227
+ expectTypeOf(
228
+ TransactionEnvelope.getType({ frames: [], type: 'eip1559' }),
229
+ ).toEqualTypeOf<'eip1559'>()
230
+ })
231
+ })
232
+
233
+ test('toRpc accepts implicit frame envelopes', () => {
234
+ const envelope = {
235
+ chainId: 1,
236
+ frames: [{}],
237
+ sender: '0x1111111111111111111111111111111111111111',
238
+ } as const
239
+ expectTypeOf(
240
+ TransactionEnvelope.toRpc(envelope),
241
+ ).toEqualTypeOf<TxEnvelopeEip8141.Rpc>()
242
+ })
243
+
244
+ test('request conversions accept frames and undefined', () => {
245
+ TransactionEnvelope.toRpc({ chainId: 1, frames: undefined, maxFeePerGas: 1n })
246
+ TransactionEnvelope.toTransactionRequest({
247
+ chainId: 1,
248
+ frames: undefined,
249
+ maxFeePerGas: 1n,
250
+ })
251
+ TransactionEnvelope.toTransactionRequest({
252
+ chainId: 1,
253
+ frames: [{}],
254
+ sender: '0x1111111111111111111111111111111111111111',
255
+ })
256
+ })
@@ -1,9 +1,12 @@
1
1
  import {
2
+ Frame,
3
+ FrameSignature,
2
4
  TransactionEnvelope,
3
5
  TxEnvelopeEip1559,
4
6
  TxEnvelopeEip2930,
5
7
  TxEnvelopeEip4844,
6
8
  TxEnvelopeEip7702,
9
+ TxEnvelopeEip8141,
7
10
  TxEnvelopeLegacy,
8
11
  } from 'ox'
9
12
  import { describe, expect, test } from 'vp/test'
@@ -62,6 +65,14 @@ const eip7702 = TxEnvelopeEip7702.from({
62
65
  value: 1n,
63
66
  })
64
67
 
68
+ const eip8141 = TxEnvelopeEip8141.from({
69
+ chainId: 1,
70
+ frames: [Frame.from({})],
71
+ sender: '0x1111111111111111111111111111111111111111',
72
+ })
73
+ const serialized8141 =
74
+ '0x06e70180941111111111111111111111111111111111111111c9c8808080c280808080c0c3808080c0'
75
+
65
76
  test('exports', () => {
66
77
  expect(Object.keys(TransactionEnvelope)).toMatchInlineSnapshot(`
67
78
  [
@@ -88,6 +99,25 @@ test('exports', () => {
88
99
  })
89
100
 
90
101
  describe('getType', () => {
102
+ test('frame fields take precedence over blobs and fees', () => {
103
+ expect(
104
+ TransactionEnvelope.getType({
105
+ blobVersionedHashes: [],
106
+ frames: [],
107
+ maxFeePerGas: 1n,
108
+ }),
109
+ ).toBe('eip8141')
110
+ expect(
111
+ TransactionEnvelope.getType({ frames: undefined, maxFeePerGas: 1n }),
112
+ ).toBe('eip1559')
113
+ expect(TransactionEnvelope.getType({ frames: [], type: 'eip1559' })).toBe(
114
+ 'eip1559',
115
+ )
116
+ expect(() => TransactionEnvelope.getType({ type: '0x6' })).toThrow(
117
+ TransactionEnvelope.InvalidTypeError,
118
+ )
119
+ })
120
+
91
121
  test('behavior: routes explicit transaction types', () => {
92
122
  expect(TransactionEnvelope.getType(legacy)).toBe('legacy')
93
123
  expect(TransactionEnvelope.getType(eip2930)).toBe('eip2930')
@@ -156,6 +186,12 @@ describe('getType', () => {
156
186
  })
157
187
 
158
188
  describe('getSerializedType', () => {
189
+ test('eip8141', () => {
190
+ expect(TransactionEnvelope.getSerializedType(serialized8141)).toBe(
191
+ 'eip8141',
192
+ )
193
+ })
194
+
159
195
  test('behavior: routes serialized transaction types', () => {
160
196
  expect(
161
197
  TransactionEnvelope.getSerializedType(TxEnvelopeLegacy.serialize(legacy)),
@@ -202,6 +238,12 @@ describe('getSerializedType', () => {
202
238
  })
203
239
 
204
240
  describe('deserialize', () => {
241
+ test('eip8141', () => {
242
+ expect(TransactionEnvelope.deserialize(serialized8141)).toEqual(
243
+ TxEnvelopeEip8141.deserialize(serialized8141),
244
+ )
245
+ })
246
+
205
247
  test('behavior: routes to the matching concrete deserializer', () => {
206
248
  const serialized = TxEnvelopeEip1559.serialize(eip1559)
207
249
 
@@ -212,6 +254,14 @@ describe('deserialize', () => {
212
254
  })
213
255
 
214
256
  describe('from', () => {
257
+ test('eip8141', () => {
258
+ const { type: _, ...input } = eip8141
259
+ expect(TransactionEnvelope.from(input)).toEqual(eip8141)
260
+ expect(TransactionEnvelope.from(serialized8141)).toEqual(
261
+ TxEnvelopeEip8141.deserialize(serialized8141),
262
+ )
263
+ })
264
+
215
265
  test('behavior: routes objects to the matching concrete converter', () => {
216
266
  expect(TransactionEnvelope.from({ maxFeePerGas: 10n, chainId: 1 })).toEqual(
217
267
  TxEnvelopeEip1559.from({ maxFeePerGas: 10n, chainId: 1 }),
@@ -244,6 +294,26 @@ describe('from', () => {
244
294
  })
245
295
 
246
296
  describe('getSignPayload', () => {
297
+ test('eip8141 signature elision', () => {
298
+ const envelope = TxEnvelopeEip8141.from({
299
+ ...eip8141,
300
+ signatures: [
301
+ FrameSignature.from('0xaabb'),
302
+ FrameSignature.from({
303
+ payload: `0x${'ab'.repeat(32)}`,
304
+ signature: '0xccdd',
305
+ }),
306
+ ],
307
+ })
308
+ expect(TransactionEnvelope.getSignPayload(envelope)).toBe(
309
+ '0x85ec4544ddb227c5425add4b606f71ddfc0852100d14066d0061a1b092bad6d3',
310
+ )
311
+ expect(TransactionEnvelope.hash(envelope, { presign: true })).toBe(
312
+ TransactionEnvelope.getSignPayload(envelope),
313
+ )
314
+ expect(envelope.signatures[0].signature).toBe('0xaabb')
315
+ })
316
+
247
317
  test('behavior: routes to the matching concrete payload helper', () => {
248
318
  expect(TransactionEnvelope.getSignPayload(eip1559)).toBe(
249
319
  TxEnvelopeEip1559.getSignPayload(eip1559),
@@ -252,6 +322,12 @@ describe('getSignPayload', () => {
252
322
  })
253
323
 
254
324
  describe('hash', () => {
325
+ test('eip8141 without outer signature fields', () => {
326
+ expect(TransactionEnvelope.hash(eip8141)).toBe(
327
+ '0xa795b6de44696d46c096aaa3d6401d6eaa21b9b639408c82be7e9e60358e4387',
328
+ )
329
+ })
330
+
255
331
  test('behavior: routes to the matching concrete hash helper', () => {
256
332
  expect(TransactionEnvelope.hash(eip1559, { presign: true })).toBe(
257
333
  TxEnvelopeEip1559.hash(eip1559, { presign: true }),
@@ -260,6 +336,10 @@ describe('hash', () => {
260
336
  })
261
337
 
262
338
  describe('serialize', () => {
339
+ test('eip8141', () => {
340
+ expect(TransactionEnvelope.serialize(eip8141)).toBe(serialized8141)
341
+ })
342
+
263
343
  test('behavior: routes to the matching concrete serializer', () => {
264
344
  expect(TransactionEnvelope.serialize(eip1559)).toBe(
265
345
  TxEnvelopeEip1559.serialize(eip1559),
@@ -284,6 +364,12 @@ describe('serialize', () => {
284
364
  })
285
365
 
286
366
  describe('toRpc', () => {
367
+ test('converts frame envelopes', () => {
368
+ expect(TransactionEnvelope.toRpc(eip8141)).toEqual(
369
+ TxEnvelopeEip8141.toRpc(eip8141),
370
+ )
371
+ })
372
+
287
373
  test('behavior: routes to the matching concrete RPC converter', () => {
288
374
  expect(TransactionEnvelope.toRpc(eip1559)).toEqual(
289
375
  TxEnvelopeEip1559.toRpc(eip1559),
@@ -292,6 +378,13 @@ describe('toRpc', () => {
292
378
  })
293
379
 
294
380
  describe('assert', () => {
381
+ test('eip8141', () => {
382
+ expect(() => TransactionEnvelope.assert(eip8141)).not.toThrow()
383
+ expect(() =>
384
+ TransactionEnvelope.assert({ ...eip8141, frames: [] }),
385
+ ).toThrow(TxEnvelopeEip8141.InvalidError)
386
+ })
387
+
295
388
  test('behavior: routes to the matching concrete assertion', () => {
296
389
  expect(() =>
297
390
  TransactionEnvelope.assert({
@@ -306,6 +399,11 @@ describe('assert', () => {
306
399
  })
307
400
 
308
401
  describe('validate', () => {
402
+ test('eip8141', () => {
403
+ expect(TransactionEnvelope.validate(eip8141)).toBe(true)
404
+ expect(TransactionEnvelope.validate({ ...eip8141, frames: [] })).toBe(false)
405
+ })
406
+
309
407
  test('behavior: returns validation result for routed envelope', () => {
310
408
  expect(TransactionEnvelope.validate(eip1559)).toBe(true)
311
409
  expect(
@@ -333,6 +431,15 @@ describe('getType', () => {
333
431
  })
334
432
 
335
433
  describe('toTransactionRequest', () => {
434
+ test('maps frame sender to request from', () => {
435
+ expect(TransactionEnvelope.toTransactionRequest(eip8141)).toEqual({
436
+ chainId: 1,
437
+ frames: [{}],
438
+ from: '0x1111111111111111111111111111111111111111',
439
+ type: 'eip8141',
440
+ })
441
+ })
442
+
336
443
  test('behavior: eip1559', () => {
337
444
  const request = TransactionEnvelope.toTransactionRequest(eip1559)
338
445
  expect(request).toMatchInlineSnapshot(`
@@ -1,4 +1,5 @@
1
1
  import { TransactionReceipt } from 'ox'
2
+ import { z } from 'ox/zod'
2
3
  import { describe, expect, test } from 'vp/test'
3
4
  import { anvilMainnet } from '../../../test/prool.js'
4
5
 
@@ -457,3 +458,61 @@ test('exports', () => {
457
458
  ]
458
459
  `)
459
460
  })
461
+
462
+ describe('frame receipts', () => {
463
+ test('preserves payer, statuses, logs, and large gas totals', () => {
464
+ const rpc = {
465
+ blockHash: '0x00',
466
+ blockNumber: '0x1',
467
+ cumulativeGasUsed: '0x20000000000001',
468
+ effectiveGasPrice: '0x1',
469
+ frameReceipts: [
470
+ {
471
+ executionGasUsed: '0x20000000000001',
472
+ logs: [
473
+ {
474
+ address: '0x1111111111111111111111111111111111111111',
475
+ data: '0xab',
476
+ topics: ['0x01'],
477
+ },
478
+ ],
479
+ stateGasUsed: '0x2',
480
+ status: 1,
481
+ },
482
+ { executionGasUsed: '0x3', logs: [], stateGasUsed: '0x0', status: 0 },
483
+ { executionGasUsed: '0x0', logs: [], stateGasUsed: '0x0', status: 2 },
484
+ ],
485
+ from: '0x1111111111111111111111111111111111111111',
486
+ gasUsed: '0x20000000000001',
487
+ logs: [],
488
+ logsBloom: '0x00',
489
+ payer: '0x2222222222222222222222222222222222222222',
490
+ status: '0x0',
491
+ to: null,
492
+ transactionHash: '0x00',
493
+ transactionIndex: '0x0',
494
+ type: '0x6',
495
+ } satisfies TransactionReceipt.Rpc
496
+ const receipt = TransactionReceipt.fromRpc(rpc)
497
+ expect(receipt.type).toBe('eip8141')
498
+ expect(receipt.payer).toBe(rpc.payer)
499
+ expect(receipt.frameReceipts).toEqual([
500
+ {
501
+ gasUsed: 9007199254740993n,
502
+ logs: rpc.frameReceipts[0]!.logs,
503
+ stateGasUsed: 2n,
504
+ status: 'success',
505
+ },
506
+ { gasUsed: 3n, logs: [], stateGasUsed: 0n, status: 'reverted' },
507
+ { gasUsed: 0n, logs: [], stateGasUsed: 0n, status: 'skipped' },
508
+ ])
509
+ expect(TransactionReceipt.toRpc(receipt)).toMatchObject(rpc)
510
+ expect(
511
+ z.decode(z.TransactionReceipt.TransactionReceipt, rpc).frameReceipts,
512
+ ).toEqual(receipt.frameReceipts)
513
+ expect(
514
+ z.encode(z.TransactionReceipt.TransactionReceipt, receipt).frameReceipts,
515
+ ).toEqual(rpc.frameReceipts)
516
+ expect(rpc.frameReceipts[0]!.executionGasUsed).toBe('0x20000000000001')
517
+ })
518
+ })