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.
- package/CHANGELOG.md +53 -0
- package/dist/core/Frame.d.ts +23 -20
- package/dist/core/Frame.d.ts.map +1 -1
- package/dist/core/Frame.js +28 -23
- package/dist/core/Frame.js.map +1 -1
- package/dist/core/FrameReceipt.d.ts +23 -14
- package/dist/core/FrameReceipt.d.ts.map +1 -1
- package/dist/core/FrameReceipt.js +15 -11
- package/dist/core/FrameReceipt.js.map +1 -1
- package/dist/core/FrameRequest.d.ts +50 -0
- package/dist/core/FrameRequest.d.ts.map +1 -0
- package/dist/core/FrameRequest.js +59 -0
- package/dist/core/FrameRequest.js.map +1 -0
- package/dist/core/FrameSignature.d.ts +17 -16
- package/dist/core/FrameSignature.d.ts.map +1 -1
- package/dist/core/FrameSignature.js +38 -21
- package/dist/core/FrameSignature.js.map +1 -1
- package/dist/core/Siwe.d.ts.map +1 -1
- package/dist/core/Siwe.js +3 -3
- package/dist/core/Siwe.js.map +1 -1
- package/dist/core/Transaction.d.ts +7 -4
- package/dist/core/Transaction.d.ts.map +1 -1
- package/dist/core/Transaction.js +8 -0
- package/dist/core/Transaction.js.map +1 -1
- package/dist/core/TransactionRequest.d.ts +8 -5
- package/dist/core/TransactionRequest.d.ts.map +1 -1
- package/dist/core/TransactionRequest.js +11 -3
- package/dist/core/TransactionRequest.js.map +1 -1
- package/dist/core/TxEnvelope.d.ts +4 -2
- package/dist/core/TxEnvelope.d.ts.map +1 -1
- package/dist/core/TxEnvelope.js +11 -2
- package/dist/core/TxEnvelope.js.map +1 -1
- package/dist/core/TxEnvelopeEip8141.d.ts +22 -11
- package/dist/core/TxEnvelopeEip8141.d.ts.map +1 -1
- package/dist/core/TxEnvelopeEip8141.js +45 -16
- package/dist/core/TxEnvelopeEip8141.js.map +1 -1
- package/dist/core/internal/frameNonce.d.ts +10 -0
- package/dist/core/internal/frameNonce.d.ts.map +1 -0
- package/dist/core/internal/frameNonce.js +45 -0
- package/dist/core/internal/frameNonce.js.map +1 -0
- package/dist/core/internal/rpcSchemas/eth.d.ts +16 -4
- package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/dist/index.d.ts +21 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -5
- package/dist/index.js.map +1 -1
- package/dist/zod/Block.d.ts +52 -37
- package/dist/zod/Block.d.ts.map +1 -1
- package/dist/zod/Frame.d.ts +14 -14
- package/dist/zod/Frame.js +5 -5
- package/dist/zod/Frame.js.map +1 -1
- package/dist/zod/FrameReceipt.d.ts +42 -2
- package/dist/zod/FrameReceipt.d.ts.map +1 -1
- package/dist/zod/FrameReceipt.js +11 -1
- package/dist/zod/FrameReceipt.js.map +1 -1
- package/dist/zod/FrameRequest.d.ts +40 -0
- package/dist/zod/FrameRequest.d.ts.map +1 -0
- package/dist/zod/FrameRequest.js +37 -0
- package/dist/zod/FrameRequest.js.map +1 -0
- package/dist/zod/FrameSignature.d.ts +21 -13
- package/dist/zod/FrameSignature.d.ts.map +1 -1
- package/dist/zod/FrameSignature.js +10 -7
- package/dist/zod/FrameSignature.js.map +1 -1
- package/dist/zod/RpcSchema.d.ts +470 -265
- package/dist/zod/RpcSchema.d.ts.map +1 -1
- package/dist/zod/Transaction.d.ts +104 -74
- package/dist/zod/Transaction.d.ts.map +1 -1
- package/dist/zod/Transaction.js +22 -15
- package/dist/zod/Transaction.js.map +1 -1
- package/dist/zod/TransactionEnvelope.d.ts +60 -34
- package/dist/zod/TransactionEnvelope.d.ts.map +1 -1
- package/dist/zod/TransactionReceipt.d.ts +42 -2
- package/dist/zod/TransactionReceipt.d.ts.map +1 -1
- package/dist/zod/TransactionRequest.d.ts +31 -21
- package/dist/zod/TransactionRequest.d.ts.map +1 -1
- package/dist/zod/TransactionRequest.js +10 -4
- package/dist/zod/TransactionRequest.js.map +1 -1
- package/dist/zod/TxEnvelopeEip8141.d.ts +44 -25
- package/dist/zod/TxEnvelopeEip8141.d.ts.map +1 -1
- package/dist/zod/TxEnvelopeEip8141.js +2 -0
- package/dist/zod/TxEnvelopeEip8141.js.map +1 -1
- package/dist/zod/internal/Frame.d.ts +8 -0
- package/dist/zod/internal/Frame.d.ts.map +1 -1
- package/dist/zod/internal/Frame.js +29 -0
- package/dist/zod/internal/Frame.js.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.d.ts +472 -267
- package/dist/zod/internal/rpcSchemas/Eth.d.ts.map +1 -1
- package/dist/zod/internal/rpcSchemas/Eth.js +9 -0
- package/dist/zod/internal/rpcSchemas/Eth.js.map +1 -1
- package/dist/zod/internal/rpcSchemas/Wallet.d.ts +51 -21
- package/dist/zod/internal/rpcSchemas/Wallet.d.ts.map +1 -1
- package/dist/zod/tempo/RpcSchemaTempo.d.ts +68 -48
- package/dist/zod/tempo/RpcSchemaTempo.d.ts.map +1 -1
- package/dist/zod/tempo/Transaction.d.ts +52 -37
- package/dist/zod/tempo/Transaction.d.ts.map +1 -1
- package/dist/zod/z.d.ts +1 -0
- package/dist/zod/z.d.ts.map +1 -1
- package/dist/zod/z.js +1 -0
- package/dist/zod/z.js.map +1 -1
- package/package.json +11 -1
- package/src/core/Frame.ts +41 -33
- package/src/core/FrameReceipt.ts +30 -16
- package/src/core/FrameRequest.ts +78 -0
- package/src/core/FrameSignature.ts +64 -34
- package/src/core/Siwe.ts +21 -13
- package/src/core/Transaction.ts +52 -24
- package/src/core/TransactionRequest.ts +21 -7
- package/src/core/TxEnvelope.ts +16 -3
- package/src/core/TxEnvelopeEip8141.ts +69 -21
- package/src/core/_test/AbiConstructor.test.ts +2 -0
- package/src/core/_test/Frame.test-d.ts +16 -7
- package/src/core/_test/Frame.test.ts +52 -24
- package/src/core/_test/FrameReceipt.test.ts +15 -9
- package/src/core/_test/FrameRequest.test-d.ts +16 -0
- package/src/core/_test/FrameRequest.test.ts +41 -0
- package/src/core/_test/FrameRpc.test.ts +131 -0
- package/src/core/_test/FrameSignature.test-d.ts +20 -0
- package/src/core/_test/FrameSignature.test.ts +132 -11
- package/src/core/_test/RpcSchema.test-d.ts +45 -0
- package/src/core/_test/Siwe.test.ts +147 -0
- package/src/core/_test/TransactionEnvelope.test-d.ts +27 -0
- package/src/core/_test/TransactionEnvelope.test.ts +27 -1
- package/src/core/_test/TransactionReceipt.test.ts +32 -6
- package/src/core/_test/TransactionRequest.test-d.ts +10 -1
- package/src/core/_test/TransactionRequest.test.ts +31 -2
- package/src/core/_test/TxEnvelopeEip8141.test-d.ts +29 -2
- package/src/core/_test/TxEnvelopeEip8141.test.ts +548 -55
- package/src/core/_test/index.test.ts +1 -0
- package/src/core/internal/frameNonce.ts +61 -0
- package/src/core/internal/rpcSchemas/eth.ts +13 -4
- package/src/index.ts +22 -5
- package/src/version.ts +1 -1
- package/src/zod/Frame.ts +5 -5
- package/src/zod/FrameReceipt.ts +11 -1
- package/src/zod/FrameRequest.ts +46 -0
- package/src/zod/FrameSignature.ts +10 -7
- package/src/zod/Transaction.ts +46 -27
- package/src/zod/TransactionRequest.ts +33 -18
- package/src/zod/TxEnvelopeEip8141.ts +2 -0
- package/src/zod/_test/Frame.test.ts +13 -11
- package/src/zod/_test/FrameReceipt.test.ts +11 -4
- package/src/zod/_test/FrameRequest.test-d.ts +15 -0
- package/src/zod/_test/FrameRequest.test.ts +44 -0
- package/src/zod/_test/FrameSignature.test.ts +73 -9
- package/src/zod/_test/RpcSchema.test.ts +106 -0
- package/src/zod/_test/TransactionRequest.test.ts +61 -0
- package/src/zod/_test/TxEnvelopeEip8141.nonce.test.ts +174 -0
- package/src/zod/_test/TxEnvelopeEip8141.test-d.ts +29 -0
- package/src/zod/_test/TxEnvelopeEip8141.test.ts +3 -3
- package/src/zod/internal/Frame.ts +45 -0
- package/src/zod/internal/rpcSchemas/Eth.ts +20 -0
- 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:
|
|
479
|
+
scheme: '0x0',
|
|
459
480
|
signature: '0xaabb',
|
|
460
|
-
signer:
|
|
481
|
+
signer: '0x',
|
|
461
482
|
}),
|
|
462
483
|
).toEqual(FrameSignature.from('0xaabb'))
|
|
463
484
|
})
|
|
464
485
|
test('secp256k1 signature', () => {
|
|
465
486
|
expect(
|
|
466
|
-
FrameSignature.fromRpc({
|
|
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:
|
|
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:
|
|
512
|
+
scheme: '0x2',
|
|
488
513
|
signature: p256Bytes,
|
|
489
514
|
})
|
|
490
515
|
})
|
|
491
516
|
test('unsigned placeholder', () => {
|
|
492
|
-
expect(
|
|
493
|
-
FrameSignature.
|
|
494
|
-
)
|
|
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:
|
|
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:
|
|
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
|
})
|