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
@@ -0,0 +1,54 @@
1
+ import { Frame } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ describe('Frame', () => {
6
+ test('decodes RPC budgets and encodes named flags', () => {
7
+ const frame = Frame.from({
8
+ flags: 'approveExecutionAndPayment',
9
+ gas: 50_000n,
10
+ mode: 'verify',
11
+ })
12
+ const rpc = z.encode(z.Frame.Frame, frame)
13
+ expect(rpc).toEqual({
14
+ data: '0x',
15
+ executionGasLimit: '0xc350',
16
+ flags: 3,
17
+ mode: 1,
18
+ stateGasLimit: '0x0',
19
+ value: '0x0',
20
+ })
21
+ expect(z.decode(z.Frame.Frame, rpc)).toEqual({
22
+ data: '0x',
23
+ flags: 3,
24
+ gas: 50_000n,
25
+ mode: 1,
26
+ stateGas: 0n,
27
+ value: 0n,
28
+ })
29
+ })
30
+ test('rejects invalid mode and budget', () => {
31
+ expect(z.safeParse(z.Frame.Decoded, { mode: 3 }).success).toBe(false)
32
+ expect(z.safeParse(z.Frame.Decoded, { gas: -1n }).success).toBe(false)
33
+ })
34
+ test('encodes numberish values', () => {
35
+ expect(
36
+ z.encode(z.Frame.FrameToRpc, { gas: '0xc350', stateGas: 0 })
37
+ .executionGasLimit,
38
+ ).toBe('0xc350')
39
+ })
40
+ })
41
+
42
+ describe('FrameToRpc validation', () => {
43
+ test.each([
44
+ { mode: 3 },
45
+ { flags: 8 },
46
+ { gas: 2n ** 64n },
47
+ { stateGas: -1 },
48
+ { gas: '0x10000000000000000' },
49
+ ])('rejects invalid frame %#', (frame) => {
50
+ expect(
51
+ z.safeEncode(z.Frame.FrameToRpc, frame as Frame.toRpc.Input).success,
52
+ ).toBe(false)
53
+ })
54
+ })
@@ -0,0 +1,75 @@
1
+ import { FrameReceipt } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+
5
+ describe('FrameReceipt', () => {
6
+ test('safe codecs reject invalid gas without throwing', () => {
7
+ expect(
8
+ z.safeDecode(z.FrameReceipt.FrameReceipt, {
9
+ executionGasUsed: '0x',
10
+ logs: [],
11
+ stateGasUsed: '0x0',
12
+ status: 1,
13
+ }).success,
14
+ ).toBe(false)
15
+ expect(
16
+ z.safeEncode(z.FrameReceipt.FrameReceipt, {
17
+ gasUsed: -1n,
18
+ logs: [],
19
+ stateGasUsed: 0n,
20
+ status: 'success',
21
+ }).success,
22
+ ).toBe(false)
23
+ for (const gasUsed of [-1, Number.MAX_SAFE_INTEGER + 1, '0x'] as const)
24
+ expect(
25
+ z.safeEncode(z.FrameReceipt.FrameReceiptToRpc, {
26
+ gasUsed,
27
+ logs: [],
28
+ stateGasUsed: 0n,
29
+ status: 'success',
30
+ }).success,
31
+ ).toBe(false)
32
+ })
33
+
34
+ test('roundtrip', () => {
35
+ const rpc = {
36
+ executionGasUsed: '0x20000000000001',
37
+ logs: [],
38
+ stateGasUsed: '0x0',
39
+ status: 2,
40
+ } as const
41
+ const receipt = FrameReceipt.fromRpc(rpc)
42
+ expect(z.decode(z.FrameReceipt.FrameReceipt, rpc)).toEqual(receipt)
43
+ expect(z.encode(z.FrameReceipt.FrameReceipt, receipt)).toEqual(rpc)
44
+ })
45
+
46
+ test('invalid status', () => {
47
+ expect(
48
+ z.safeDecode(z.FrameReceipt.FrameReceipt, {
49
+ executionGasUsed: '0x0',
50
+ logs: [],
51
+ stateGasUsed: '0x0',
52
+ // @ts-expect-error Invalid RPC status.
53
+ status: 3,
54
+ }).success,
55
+ ).toBe(false)
56
+ })
57
+ })
58
+
59
+ describe('FrameReceiptToRpc', () => {
60
+ test('numberish gas', () => {
61
+ expect(
62
+ z.encode(z.FrameReceipt.FrameReceiptToRpc, {
63
+ gasUsed: 21000,
64
+ logs: [],
65
+ stateGasUsed: '0x0',
66
+ status: 'success',
67
+ }),
68
+ ).toEqual({
69
+ executionGasUsed: '0x5208',
70
+ logs: [],
71
+ stateGasUsed: '0x0',
72
+ status: 1,
73
+ })
74
+ })
75
+ })
@@ -0,0 +1,94 @@
1
+ import { FrameSignature, P256, Secp256k1, Hash } from 'ox'
2
+ import { z } from 'ox/zod'
3
+ import { describe, expect, test } from 'vp/test'
4
+ import { accounts } from '../../../test/constants/accounts.js'
5
+
6
+ describe('FrameSignature', () => {
7
+ test('safeDecode rejects malformed protocol signatures without throwing', () => {
8
+ expect(
9
+ z.safeDecode(z.FrameSignature.FrameSignature, {
10
+ msg: '0x',
11
+ scheme: 1,
12
+ signature: '0x01',
13
+ }).success,
14
+ ).toBe(false)
15
+ })
16
+
17
+ test('encodes omitted payloads', () => {
18
+ expect(
19
+ z.encode(z.FrameSignature.FrameSignature, {
20
+ scheme: 'arbitrary',
21
+ signature: '0xaabb',
22
+ }),
23
+ ).toEqual({ msg: '0x', scheme: 0, signature: '0xaabb' })
24
+ expect(
25
+ z.encode(z.FrameSignature.FrameSignature, {
26
+ scheme: 'secp256k1',
27
+ }),
28
+ ).toEqual({ msg: '0x', scheme: 1, signature: '0x' })
29
+ expect(
30
+ z.encode(z.FrameSignature.FrameSignature, {
31
+ scheme: 'p256',
32
+ }),
33
+ ).toEqual({ msg: '0x', scheme: 2, signature: '0x' })
34
+ })
35
+
36
+ test('arbitrary bytes', () => {
37
+ expect(
38
+ z.encode(z.FrameSignature.FrameSignature, FrameSignature.from('0xaabb')),
39
+ ).toEqual({ msg: '0x', scheme: 0, signature: '0xaabb' })
40
+ expect(
41
+ z.decode(z.FrameSignature.FrameSignature, {
42
+ msg: '0x',
43
+ scheme: 0,
44
+ signature: '0xaabb',
45
+ }),
46
+ ).toEqual(FrameSignature.from('0xaabb'))
47
+ })
48
+ test('secp256k1 signature', () => {
49
+ const entry = FrameSignature.from({
50
+ scheme: 'secp256k1',
51
+ signature: Secp256k1.sign({
52
+ payload: Hash.keccak256('0xdeadbeef'),
53
+ privateKey: accounts[0].privateKey,
54
+ }),
55
+ })
56
+ const rpc = z.encode(z.FrameSignature.FrameSignature, entry)
57
+ expect(rpc.scheme).toBe(1)
58
+ expect(z.decode(z.FrameSignature.FrameSignature, rpc)).toEqual(entry)
59
+ })
60
+ test('P-256 signature', () => {
61
+ const privateKey = accounts[0].privateKey
62
+ const entry = FrameSignature.from({
63
+ publicKey: P256.getPublicKey({ privateKey }),
64
+ scheme: 'p256',
65
+ signature: P256.sign({
66
+ payload: Hash.keccak256('0xdeadbeef'),
67
+ privateKey,
68
+ }),
69
+ })
70
+ const rpc = z.encode(z.FrameSignature.FrameSignature, entry)
71
+ expect(rpc.scheme).toBe(2)
72
+ expect(z.decode(z.FrameSignature.FrameSignature, rpc)).toEqual({
73
+ ...entry,
74
+ signature: { r: entry.signature.r, s: entry.signature.s },
75
+ })
76
+ })
77
+ test('rejects missing P-256 public key and invalid arbitrary signer', () => {
78
+ expect(
79
+ z.safeParse(z.FrameSignature.Decoded, {
80
+ payload: '0x',
81
+ scheme: 'p256',
82
+ signature: { r: '0x01', s: '0x02' },
83
+ }).success,
84
+ ).toBe(false)
85
+ expect(
86
+ z.safeParse(z.FrameSignature.Decoded, {
87
+ payload: '0x',
88
+ scheme: 'arbitrary',
89
+ signature: '0x',
90
+ signer: accounts[0].address,
91
+ }).success,
92
+ ).toBe(false)
93
+ })
94
+ })
@@ -0,0 +1,116 @@
1
+ import {
2
+ Frame,
3
+ FrameSignature,
4
+ TransactionEnvelope,
5
+ TxEnvelopeEip8141,
6
+ } from 'ox'
7
+ import { z } from 'ox/zod'
8
+ import { describe, expect, test } from 'vp/test'
9
+ import { accounts } from '../../../test/constants/accounts.js'
10
+
11
+ describe('TxEnvelopeEip8141', () => {
12
+ test('signed codecs reject protocol signature placeholders', () => {
13
+ const envelope = TxEnvelopeEip8141.from({
14
+ chainId: 1,
15
+ frames: [{}],
16
+ sender: accounts[0].address,
17
+ signatures: [{ scheme: 'secp256k1' }],
18
+ })
19
+ expect(
20
+ z.safeDecode(
21
+ z.TransactionEnvelope.Signed,
22
+ TxEnvelopeEip8141.toRpc(envelope),
23
+ ).success,
24
+ ).toBe(false)
25
+ expect(z.safeEncode(z.TransactionEnvelope.Signed, envelope).success).toBe(
26
+ false,
27
+ )
28
+ expect(
29
+ z.safeEncode(z.TransactionEnvelope.SignedToRpc, envelope).success,
30
+ ).toBe(false)
31
+ expect(
32
+ z.safeParse(z.TransactionEnvelope.SignedDecoded, envelope).success,
33
+ ).toBe(false)
34
+ })
35
+
36
+ test('RPC codec preserves chain ID precision and signing payloads', () => {
37
+ const envelope = TxEnvelopeEip8141.from({
38
+ chainId: 9007199254740993n,
39
+ frames: [Frame.from({})],
40
+ sender: accounts[0].address,
41
+ signatures: [FrameSignature.from({ scheme: 'secp256k1' })],
42
+ })
43
+ const rpc = z.encode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, envelope)
44
+ expect(rpc.chainId).toBe('0x20000000000001')
45
+ const decoded = z.decode(z.TransactionEnvelope.TransactionEnvelope, rpc)
46
+ expect(decoded.chainId).toBe(9007199254740993n)
47
+ expect(TransactionEnvelope.getSignPayload(decoded)).toBe(
48
+ TxEnvelopeEip8141.getSignPayload(envelope),
49
+ )
50
+ })
51
+ test('generic serialized codec supports unsigned frame entries', () => {
52
+ const envelope = TxEnvelopeEip8141.from({
53
+ chainId: 1,
54
+ frames: [Frame.from({})],
55
+ sender: '0x1111111111111111111111111111111111111111',
56
+ })
57
+ const serialized =
58
+ '0x06e70180941111111111111111111111111111111111111111c9c8808080c280808080c0c3808080c0'
59
+ expect(z.encode(z.TxEnvelopeEip8141.serialized, envelope)).toBe(serialized)
60
+ const decoded = z.decode(z.TransactionEnvelope.serialized, serialized)
61
+ expect(decoded.type).toBe('eip8141')
62
+ expect(z.encode(z.TransactionEnvelope.serialized, decoded)).toBe(serialized)
63
+ })
64
+ test('rejects invalid envelope constraints', () => {
65
+ expect(
66
+ z.safeParse(z.TxEnvelopeEip8141.Decoded, {
67
+ chainId: 1,
68
+ frames: [],
69
+ sender: accounts[0].address,
70
+ type: 'eip8141',
71
+ }).success,
72
+ ).toBe(false)
73
+ })
74
+ test('numberish encoder preserves defaults', () => {
75
+ expect(
76
+ z.encode(z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc, {
77
+ chainId: '0x1',
78
+ frames: [{ gas: 50_000 }],
79
+ sender: accounts[0].address,
80
+ type: 'eip8141',
81
+ }).frames[0]!.executionGasLimit,
82
+ ).toBe('0xc350')
83
+ })
84
+ })
85
+
86
+ describe('invalid envelopes', () => {
87
+ const envelope = TxEnvelopeEip8141.from({
88
+ chainId: 1,
89
+ frames: [Frame.from({})],
90
+ sender: accounts[0].address,
91
+ })
92
+ test.each([
93
+ { frames: [] },
94
+ { frames: Array.from({ length: 65 }, () => Frame.from({})) },
95
+ { maxFeePerBlobGas: 1n },
96
+ { maxFeePerGas: 1n, maxPriorityFeePerGas: 2n },
97
+ ])('rejects numberish envelope %#', (fields) => {
98
+ const invalid = { ...envelope, ...fields }
99
+ expect(
100
+ z.safeEncode(z.TxEnvelopeEip8141.TxEnvelopeEip8141ToRpc, invalid).success,
101
+ ).toBe(false)
102
+ const rpc = TxEnvelopeEip8141.toRpc(invalid)
103
+ expect(
104
+ z.safeDecode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, rpc).success,
105
+ ).toBe(false)
106
+ })
107
+ test('rejects malformed nested RPC signatures without throwing', () => {
108
+ const rpc = {
109
+ ...TxEnvelopeEip8141.toRpc(envelope),
110
+ signatures: [{ msg: '0x', scheme: 1, signature: '0x01' }],
111
+ } as const
112
+ expect(
113
+ z.safeDecode(z.TxEnvelopeEip8141.TxEnvelopeEip8141, rpc).success,
114
+ ).toBe(false)
115
+ })
116
+ })
@@ -0,0 +1,26 @@
1
+ /* eslint-disable jsdoc-js/require-jsdoc, jsdoc-js/require-description, jsdoc-js/require-example */
2
+ import * as Hex from '../../core/Hex.js'
3
+ import * as Quantity from '../../core/internal/quantity.js'
4
+ import { quantityHex, uintBigint, uintNumber } from './Integer.js'
5
+ import * as z from 'zod/mini'
6
+
7
+ export const chainId = z.codec(
8
+ quantityHex(),
9
+ z.union([uintNumber(), uintBigint()]),
10
+ {
11
+ decode(value) {
12
+ const id = Hex.toBigInt(value)
13
+ return id <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(id) : id
14
+ },
15
+ encode: (value) => Hex.fromNumber(value),
16
+ },
17
+ )
18
+
19
+ export const chainIdToRpc = z.codec(
20
+ quantityHex(),
21
+ z.union([quantityHex(), uintNumber(), uintBigint()]),
22
+ {
23
+ decode: (value) => z.decode(chainId, value),
24
+ encode: (value) => Quantity.fromNumberish(value),
25
+ },
26
+ )
@@ -9,16 +9,6 @@ import {
9
9
  import * as z from 'zod/mini'
10
10
  import * as z_SignatureEnvelope from './SignatureEnvelope.js'
11
11
 
12
- const SignatureRpc = z.union([
13
- z_SignatureEnvelope.PrimitiveRpc,
14
- z_SignatureEnvelope.MultisigRpc,
15
- ])
16
-
17
- const Signature = z.union([
18
- z_SignatureEnvelope.Primitive,
19
- z_SignatureEnvelope.Multisig,
20
- ])
21
-
22
12
  /** RPC token spending limit schema. */
23
13
  export const RpcTokenLimit = z.object({
24
14
  limit: z_Hex.Hex,
@@ -39,18 +29,28 @@ export const RpcCallScope = z.object({
39
29
  })
40
30
 
41
31
  /** RPC key authorization schema. */
42
- export const Rpc = z.object({
43
- account: z.optional(z.nullable(z_Address.Address)),
44
- allowedCalls: z.optional(z.nullable(z.readonly(z.array(RpcCallScope)))),
45
- chainId: z_Hex.Hex,
46
- expiry: z.optional(z.nullable(z_Hex.Hex)),
47
- isAdmin: z.optional(z.nullable(z.boolean())),
48
- keyId: z_Address.Address,
49
- keyType: z_SignatureEnvelope.Type,
50
- limits: z.optional(z.nullable(z.readonly(z.array(RpcTokenLimit)))),
51
- signature: SignatureRpc,
52
- witness: z.optional(z.nullable(z_Hex.Hex)),
53
- })
32
+ export const Rpc = z
33
+ .object({
34
+ account: z.optional(z.nullable(z_Address.Address)),
35
+ allowedCalls: z.optional(z.nullable(z.readonly(z.array(RpcCallScope)))),
36
+ chainId: z_Hex.Hex,
37
+ expiry: z.optional(z.nullable(z_Hex.Hex)),
38
+ isAdmin: z.optional(z.nullable(z.boolean())),
39
+ keyId: z_Address.Address,
40
+ keyType: z.union([z_SignatureEnvelope.Type, z.literal('multisig')]),
41
+ limits: z.optional(z.nullable(z.readonly(z.array(RpcTokenLimit)))),
42
+ signature: z.union([
43
+ z_SignatureEnvelope.PrimitiveRpc,
44
+ z_SignatureEnvelope.MultisigRpc,
45
+ ]),
46
+ witness: z.optional(z.nullable(z_Hex.Hex)),
47
+ })
48
+ .check(
49
+ z.refine(
50
+ (value) => value.keyType !== 'multisig' || value.account != null,
51
+ 'multisig key grants require a parent account binding',
52
+ ),
53
+ )
54
54
 
55
55
  /** Token spending limit schema. */
56
56
  export const TokenLimit = z.object({
@@ -79,20 +79,27 @@ const domainShape = {
79
79
  expiry: z.optional(z.number()),
80
80
  limits: z.optional(z.readonly(z.array(TokenLimit))),
81
81
  scopes: z.optional(z.readonly(z.array(Scope))),
82
- signature: Signature,
83
- type: z_SignatureEnvelope.Type,
82
+ signature: z.union([
83
+ z_SignatureEnvelope.Primitive,
84
+ z_SignatureEnvelope.Multisig,
85
+ ]),
86
+ type: z.union([z_SignatureEnvelope.Type, z.literal('multisig')]),
84
87
  witness: z.optional(z_Hex.Hex),
85
88
  }
86
89
 
87
- /** Decoded key authorization schema (TIP-1049 admin fields are paired: both or neither). */
88
- export const Domain = z.union([
89
- z.object({
90
+ /** Decoded key authorization schema. */
91
+ export const Domain = z
92
+ .object({
90
93
  ...domainShape,
91
- account: z_Address.Address,
92
- isAdmin: z.boolean(),
93
- }),
94
- z.object(domainShape),
95
- ])
94
+ account: z.optional(z_Address.Address),
95
+ isAdmin: z.optional(z.boolean()),
96
+ })
97
+ .check(
98
+ z.refine(
99
+ (value) => value.type !== 'multisig' || value.account != null,
100
+ 'multisig key grants require a parent account binding',
101
+ ),
102
+ )
96
103
 
97
104
  const domainToRpcShape = {
98
105
  ...domainShape,
@@ -102,14 +109,18 @@ const domainToRpcShape = {
102
109
  }
103
110
 
104
111
  /** Encode-only decoded key authorization schema accepting numberish `toRpc` inputs. */
105
- export const DomainToRpc = z.union([
106
- z.object({
112
+ export const DomainToRpc = z
113
+ .object({
107
114
  ...domainToRpcShape,
108
- account: z_Address.Address,
109
- isAdmin: z.boolean(),
110
- }),
111
- z.object(domainToRpcShape),
112
- ])
115
+ account: z.optional(z_Address.Address),
116
+ isAdmin: z.optional(z.boolean()),
117
+ })
118
+ .check(
119
+ z.refine(
120
+ (value) => value.type !== 'multisig' || value.account != null,
121
+ 'multisig key grants require a parent account binding',
122
+ ),
123
+ )
113
124
 
114
125
  /** Codec decoding an RPC key authorization into a signed key authorization. */
115
126
  export const KeyAuthorization = z.codec(Rpc, Domain, {
@@ -7,53 +7,17 @@ import * as z from 'zod/mini'
7
7
  import * as z_MultisigConfig from './MultisigConfig.js'
8
8
  import * as z_SignatureEnvelope from './SignatureEnvelope.js'
9
9
 
10
- const NestedPrimitiveApproval = z.object({
10
+ const PrimitiveApproval = z.strictObject({
11
11
  keyData: z.optional(z_Hex.Hex),
12
12
  keyType: z.optional(z_SignatureEnvelope.Type),
13
13
  owner: z_Address.Address,
14
14
  })
15
15
 
16
- const PrimitiveApproval = z.object({
17
- keyData: z.optional(z_Hex.Hex),
18
- keyType: z.optional(z_SignatureEnvelope.Type),
19
- owner: z_Address.Address,
20
- type: z.literal('primitive'),
21
- })
22
-
23
- const NestedSpec = z.object({
24
- account: z_Address.Address,
25
- approvals: z.readonly(
26
- z
27
- .array(NestedPrimitiveApproval)
28
- .check(z.maxLength(core_MultisigConfig.maxSignatures)),
29
- ),
30
- config: z_MultisigConfig.Config,
31
- })
32
-
33
- const NestedSpecRpc = z.object({
34
- account: z_Address.Address,
35
- approvals: z.readonly(
36
- z
37
- .array(NestedPrimitiveApproval)
38
- .check(z.maxLength(core_MultisigConfig.maxSignatures)),
39
- ),
40
- config: z_Hex.Hex,
41
- })
42
-
43
16
  /** Native multisig simulation spec schema. */
44
17
  export const Domain = z.object({
45
- account: z_Address.Address,
46
18
  approvals: z.readonly(
47
19
  z
48
- .array(
49
- z.union([
50
- PrimitiveApproval,
51
- z.object({
52
- spec: NestedSpec,
53
- type: z.literal('multisig'),
54
- }),
55
- ]),
56
- )
20
+ .array(PrimitiveApproval)
57
21
  .check(z.maxLength(core_MultisigConfig.maxSignatures)),
58
22
  ),
59
23
  config: z_MultisigConfig.Config,
@@ -61,18 +25,9 @@ export const Domain = z.object({
61
25
 
62
26
  const Rpc_ = z
63
27
  .object({
64
- account: z_Address.Address,
65
28
  approvals: z.readonly(
66
29
  z
67
- .array(
68
- z.union([
69
- PrimitiveApproval,
70
- z.object({
71
- spec: NestedSpecRpc,
72
- type: z.literal('multisig'),
73
- }),
74
- ]),
75
- )
30
+ .array(PrimitiveApproval)
76
31
  .check(z.maxLength(core_MultisigConfig.maxSignatures)),
77
32
  ),
78
33
  config: z_Hex.Hex,
@@ -72,23 +72,35 @@ export const WebAuthnRpc = z.object({
72
72
  export const PrimitiveRpc = z.union([Secp256k1Rpc, P256Rpc, WebAuthnRpc])
73
73
 
74
74
  /** RPC keychain signature envelope schema. */
75
- export const KeychainRpc = z.object({
76
- keyId: z.optional(z_Address.Address),
77
- // `signature` is recursive; type the getter concretely to break the cycle.
78
- signature: z.lazy(
79
- (): z.ZodMiniType<core_SignatureEnvelope.SignatureEnvelopeRpc> =>
80
- Rpc as never,
81
- ),
82
- type: z.literal('keychain'),
83
- userAddress: z_Address.Address,
84
- version: z.optional(KeychainVersion),
85
- })
75
+ export const KeychainRpc = z
76
+ .object({
77
+ keyId: z.optional(z_Address.Address),
78
+ // `signature` is recursive; type the getter concretely to break the cycle.
79
+ signature: z.lazy(
80
+ (): z.ZodMiniType<core_SignatureEnvelope.SignatureEnvelopeRpc> =>
81
+ Rpc as never,
82
+ ),
83
+ type: z.literal('keychain'),
84
+ userAddress: z_Address.Address,
85
+ version: z.optional(KeychainVersion),
86
+ })
87
+ .check(
88
+ z.refine((value) => {
89
+ try {
90
+ core_SignatureEnvelope.fromRpc(value)
91
+ return true
92
+ } catch {
93
+ return false
94
+ }
95
+ }, 'multisig access keys require keychain V2'),
96
+ )
86
97
 
87
98
  /** RPC native multisig signature envelope schema. */
88
99
  export const MultisigRpc = z_Hex.Hex.check(
89
100
  z.refine((value) => {
90
101
  try {
91
- return core_SignatureEnvelope.fromRpc(value).type === 'multisig'
102
+ core_SignatureEnvelope.fromRpc(value)
103
+ return true
92
104
  } catch {
93
105
  return false
94
106
  }
@@ -133,34 +145,34 @@ export const WebAuthn = z.object({
133
145
  export const Primitive = z.union([Secp256k1, P256, WebAuthn])
134
146
 
135
147
  /** Keychain signature envelope schema. */
136
- export const Keychain = z.object({
137
- // `inner` is recursive; type the getter concretely to break the cycle.
138
- inner: z.lazy(
139
- (): z.ZodMiniType<core_SignatureEnvelope.SignatureEnvelope> =>
140
- Domain as never,
141
- ),
142
- keyId: z.optional(z_Address.Address),
143
- type: z.literal('keychain'),
144
- userAddress: z_Address.Address,
145
- version: z.optional(KeychainVersion),
146
- })
148
+ export const Keychain = z
149
+ .object({
150
+ // `inner` is recursive; type the getter concretely to break the cycle.
151
+ inner: z.lazy(
152
+ (): z.ZodMiniType<core_SignatureEnvelope.SignatureEnvelope> =>
153
+ Domain as never,
154
+ ),
155
+ keyId: z.optional(z_Address.Address),
156
+ type: z.literal('keychain'),
157
+ userAddress: z_Address.Address,
158
+ version: z.optional(KeychainVersion),
159
+ })
160
+ .check(
161
+ z.refine(
162
+ (value) => core_SignatureEnvelope.validate(value),
163
+ 'multisig access keys require keychain V2',
164
+ ),
165
+ )
147
166
 
148
167
  /** Native multisig signature envelope schema. */
149
168
  export const Multisig = z
150
169
  .object({
151
170
  account: z_Address.Address,
152
171
  config: z_MultisigConfig.Config,
153
- // `signatures` is recursive; type the getter concretely to break the cycle.
154
- signatures: z.lazy(
155
- (): z.ZodMiniType<readonly core_SignatureEnvelope.SignatureEnvelope[]> =>
156
- z.readonly(
157
- z
158
- .array(Domain)
159
- .check(
160
- z.minLength(1),
161
- z.maxLength(core_MultisigConfig.maxSignatures),
162
- ),
163
- ) as never,
172
+ signatures: z.readonly(
173
+ z
174
+ .array(Primitive)
175
+ .check(z.minLength(1), z.maxLength(core_MultisigConfig.maxSignatures)),
164
176
  ),
165
177
  type: z.literal('multisig'),
166
178
  })
@@ -189,7 +201,6 @@ function fromRpc(
189
201
  value: core_SignatureEnvelope.SignatureEnvelopeRpc,
190
202
  ): core_SignatureEnvelope.SignatureEnvelope {
191
203
  if (typeof value === 'string') return core_SignatureEnvelope.fromRpc(value)
192
-
193
204
  if (value.type === 'secp256k1') {
194
205
  const secp256k1 = value as core_SignatureEnvelope.Secp256k1Rpc
195
206
  return {