ox 1.8.12 → 1.8.13
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 +16 -0
- package/dist/core/TxEnvelope.d.ts.map +1 -1
- package/dist/core/TxEnvelope.js +5 -3
- package/dist/core/TxEnvelope.js.map +1 -1
- package/dist/core/TxEnvelopeEip1559.d.ts.map +1 -1
- package/dist/core/TxEnvelopeEip1559.js +6 -2
- package/dist/core/TxEnvelopeEip1559.js.map +1 -1
- package/dist/core/TxEnvelopeEip4844.d.ts.map +1 -1
- package/dist/core/TxEnvelopeEip4844.js +4 -0
- package/dist/core/TxEnvelopeEip4844.js.map +1 -1
- package/dist/core/TxEnvelopeEip7702.d.ts.map +1 -1
- package/dist/core/TxEnvelopeEip7702.js +4 -0
- package/dist/core/TxEnvelopeEip7702.js.map +1 -1
- package/dist/core/TypedData.d.ts +8 -1
- package/dist/core/TypedData.d.ts.map +1 -1
- package/dist/core/TypedData.js +51 -14
- package/dist/core/TypedData.js.map +1 -1
- package/dist/core/internal/abiItem.d.ts.map +1 -1
- package/dist/core/internal/abiItem.js +44 -13
- package/dist/core/internal/abiItem.js.map +1 -1
- package/dist/core/internal/abiParameters.d.ts +2 -2
- package/dist/core/internal/abiParameters.d.ts.map +1 -1
- package/dist/core/internal/abiParameters.js +24 -7
- package/dist/core/internal/abiParameters.js.map +1 -1
- package/dist/core/internal/rpcSchemas/eth.d.ts +3 -2
- package/dist/core/internal/rpcSchemas/eth.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.d.ts +21 -6
- package/dist/tempo/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/KeyAuthorization.js +7 -4
- package/dist/tempo/KeyAuthorization.js.map +1 -1
- package/dist/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/dist/tempo/TxEnvelopeTempo.js +6 -2
- package/dist/tempo/TxEnvelopeTempo.js.map +1 -1
- package/package.json +5 -2
- package/src/core/TxEnvelope.ts +5 -3
- package/src/core/TxEnvelopeEip1559.ts +8 -2
- package/src/core/TxEnvelopeEip4844.ts +6 -0
- package/src/core/TxEnvelopeEip7702.ts +6 -0
- package/src/core/TypedData.ts +57 -13
- package/src/core/_test/AbiItem.test.ts +238 -4
- package/src/core/_test/AbiParameters.decode.test.ts +140 -1
- package/src/core/_test/RpcSchema.test-d.ts +23 -1
- package/src/core/_test/TransactionEnvelopeEip1559.test.ts +24 -0
- package/src/core/_test/TransactionEnvelopeEip4844.test.ts +13 -0
- package/src/core/_test/TransactionEnvelopeEip7702.test.ts +13 -0
- package/src/core/_test/TypedData.test.ts +188 -1
- package/src/core/internal/abiItem.ts +48 -17
- package/src/core/internal/abiParameters.ts +26 -8
- package/src/core/internal/rpcSchemas/eth.ts +2 -1
- package/src/tempo/KeyAuthorization.test.ts +6 -4
- package/src/tempo/KeyAuthorization.ts +26 -8
- package/src/tempo/TxEnvelopeTempo.test.ts +26 -0
- package/src/tempo/TxEnvelopeTempo.ts +8 -2
- package/src/tempo/e2e.test.ts +73 -17
- package/src/version.ts +1 -1
|
@@ -425,7 +425,7 @@ describe('assert', () => {
|
|
|
425
425
|
).toThrowErrorMatchingInlineSnapshot(`
|
|
426
426
|
[TypedData.InvalidStructTypeError: Struct type "address" is invalid.
|
|
427
427
|
|
|
428
|
-
Struct type must not be a Solidity type.]
|
|
428
|
+
Struct type must contain only letters, digits, "_" and "$", and must not be a Solidity type.]
|
|
429
429
|
`)
|
|
430
430
|
})
|
|
431
431
|
|
|
@@ -498,6 +498,161 @@ describe('assert', () => {
|
|
|
498
498
|
[Hex.IntegerOutOfRangeError: Number \`256n\` is not in safe 8-bit unsigned integer range (\`0n\` to \`255n\`)]
|
|
499
499
|
`)
|
|
500
500
|
})
|
|
501
|
+
test.each([
|
|
502
|
+
'1Order',
|
|
503
|
+
'_Order',
|
|
504
|
+
'bytesInfo',
|
|
505
|
+
'Foo$Bar',
|
|
506
|
+
'intent',
|
|
507
|
+
'interval',
|
|
508
|
+
'uintPair',
|
|
509
|
+
])('struct type: %s', (type) => {
|
|
510
|
+
TypedData.assert({
|
|
511
|
+
types: {
|
|
512
|
+
Order: [{ name: 'inner', type }],
|
|
513
|
+
[type]: [{ name: 'amount', type: 'uint256' }],
|
|
514
|
+
},
|
|
515
|
+
primaryType: 'Order',
|
|
516
|
+
message: { inner: { amount: 1n } },
|
|
517
|
+
} as any)
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
test.each([
|
|
521
|
+
'address',
|
|
522
|
+
'bool',
|
|
523
|
+
'bytes',
|
|
524
|
+
'bytes32',
|
|
525
|
+
'bytes32[]',
|
|
526
|
+
'bytes33',
|
|
527
|
+
'Foo.Bar',
|
|
528
|
+
'int',
|
|
529
|
+
'int8',
|
|
530
|
+
'int8[2]',
|
|
531
|
+
'string',
|
|
532
|
+
'uint',
|
|
533
|
+
'uint256',
|
|
534
|
+
'uint256 ',
|
|
535
|
+
'uint256[]',
|
|
536
|
+
'uint7',
|
|
537
|
+
])('invalid struct type: %s', (type) => {
|
|
538
|
+
expect(() =>
|
|
539
|
+
TypedData.assert({
|
|
540
|
+
types: {
|
|
541
|
+
Order: [{ name: 'inner', type }],
|
|
542
|
+
[type]: [{ name: 'amount', type: 'uint256' }],
|
|
543
|
+
},
|
|
544
|
+
primaryType: 'Order',
|
|
545
|
+
message: { inner: { amount: 1n } },
|
|
546
|
+
} as any),
|
|
547
|
+
).toThrowError(`Struct type "${type}" is invalid.`)
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
test('invalid struct type: array element', () => {
|
|
551
|
+
expect(() =>
|
|
552
|
+
TypedData.assert({
|
|
553
|
+
types: {
|
|
554
|
+
Message: [{ name: 'contents', type: 'bytes32[]' }],
|
|
555
|
+
bytes32: [],
|
|
556
|
+
},
|
|
557
|
+
primaryType: 'Message',
|
|
558
|
+
message: { contents: [Hex.padLeft('0x11', 32)] },
|
|
559
|
+
} as any),
|
|
560
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
561
|
+
[TypedData.InvalidStructTypeError: Struct type "bytes32" is invalid.
|
|
562
|
+
|
|
563
|
+
Struct type must contain only letters, digits, "_" and "$", and must not be a Solidity type.]
|
|
564
|
+
`)
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
test.each([
|
|
568
|
+
'bytes0',
|
|
569
|
+
'bytes33',
|
|
570
|
+
'int0',
|
|
571
|
+
'int7',
|
|
572
|
+
'uint08',
|
|
573
|
+
'uint7',
|
|
574
|
+
'uint7[]',
|
|
575
|
+
'uint264',
|
|
576
|
+
'uint264[2]',
|
|
577
|
+
])('invalid type: %s', (type) => {
|
|
578
|
+
expect(() =>
|
|
579
|
+
TypedData.assert({
|
|
580
|
+
types: {
|
|
581
|
+
Message: [{ name: 'amount', type }],
|
|
582
|
+
},
|
|
583
|
+
primaryType: 'Message',
|
|
584
|
+
message: {
|
|
585
|
+
amount: type.includes('[')
|
|
586
|
+
? [1n, 2n]
|
|
587
|
+
: type.startsWith('bytes')
|
|
588
|
+
? '0x'
|
|
589
|
+
: 1n,
|
|
590
|
+
},
|
|
591
|
+
} as any),
|
|
592
|
+
).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
test('invalid type: alias', () => {
|
|
596
|
+
expect(() =>
|
|
597
|
+
TypedData.assert({
|
|
598
|
+
types: {
|
|
599
|
+
Message: [{ name: 'amount', type: 'uint' }],
|
|
600
|
+
},
|
|
601
|
+
primaryType: 'Message',
|
|
602
|
+
message: { amount: 1n },
|
|
603
|
+
} as any),
|
|
604
|
+
).toThrowErrorMatchingInlineSnapshot(`
|
|
605
|
+
[TypedData.InvalidTypeError: Type "uint" is not a valid EIP-712 type.
|
|
606
|
+
|
|
607
|
+
Use "uint256" instead.]
|
|
608
|
+
`)
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
test('invalid type: undefined struct', () => {
|
|
612
|
+
expect(() =>
|
|
613
|
+
TypedData.assert({
|
|
614
|
+
types: {
|
|
615
|
+
Mail: [{ name: 'from', type: 'Person' }],
|
|
616
|
+
},
|
|
617
|
+
primaryType: 'Mail',
|
|
618
|
+
message: { from: { name: 'Cow' } },
|
|
619
|
+
} as any),
|
|
620
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
621
|
+
`[TypedData.InvalidTypeError: Type "Person" is not a valid EIP-712 type.]`,
|
|
622
|
+
)
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
test.each(['bytes0', 'Foo', 'uint', 'uint7', 'uint7[]'])(
|
|
626
|
+
'invalid type in array element struct: %s',
|
|
627
|
+
(type) => {
|
|
628
|
+
for (const to of ['Person[]', 'Person[][]', 'Person[2]'])
|
|
629
|
+
expect(() =>
|
|
630
|
+
TypedData.assert({
|
|
631
|
+
types: {
|
|
632
|
+
Mail: [{ name: 'to', type: to }],
|
|
633
|
+
Person: [{ name: 'x', type }],
|
|
634
|
+
},
|
|
635
|
+
primaryType: 'Mail',
|
|
636
|
+
message: { to: [] },
|
|
637
|
+
} as any),
|
|
638
|
+
).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
|
|
639
|
+
},
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
test.each(['__proto__', 'constructor', 'hasOwnProperty', 'toString'])(
|
|
643
|
+
'invalid type: %s',
|
|
644
|
+
(type) => {
|
|
645
|
+
expect(() =>
|
|
646
|
+
TypedData.assert({
|
|
647
|
+
types: {
|
|
648
|
+
Message: [{ name: 'value', type }],
|
|
649
|
+
},
|
|
650
|
+
primaryType: 'Message',
|
|
651
|
+
message: { value: {} },
|
|
652
|
+
} as any),
|
|
653
|
+
).toThrowError(`Type "${type}" is not a valid EIP-712 type.`)
|
|
654
|
+
},
|
|
655
|
+
)
|
|
501
656
|
})
|
|
502
657
|
|
|
503
658
|
describe('domainSeparator', () => {
|
|
@@ -1054,6 +1209,37 @@ describe('getSignPayload', () => {
|
|
|
1054
1209
|
`"0xd2669f23b7849020ad41bcbff5b51372793f91320e0f901641945568ed7322be"`,
|
|
1055
1210
|
)
|
|
1056
1211
|
})
|
|
1212
|
+
test('struct type: prefixed by a Solidity type', () => {
|
|
1213
|
+
expect(
|
|
1214
|
+
TypedData.getSignPayload({
|
|
1215
|
+
domain: { name: 'Domain' },
|
|
1216
|
+
types: {
|
|
1217
|
+
Message: [{ name: 'intent', type: 'intent' }],
|
|
1218
|
+
intent: [{ name: 'amount', type: 'uint256' }],
|
|
1219
|
+
},
|
|
1220
|
+
primaryType: 'Message',
|
|
1221
|
+
message: { intent: { amount: 1n } },
|
|
1222
|
+
}),
|
|
1223
|
+
).toMatchInlineSnapshot(
|
|
1224
|
+
`"0xcbeb995e95e87817f47e51736d0e5825644055cbcad196c2b26449ad78fd87ee"`,
|
|
1225
|
+
)
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
test('struct type: with $', () => {
|
|
1229
|
+
expect(
|
|
1230
|
+
TypedData.getSignPayload({
|
|
1231
|
+
domain: { name: 'Domain' },
|
|
1232
|
+
types: {
|
|
1233
|
+
Message: [{ name: 'order', type: 'Order$1' }],
|
|
1234
|
+
Order$1: [{ name: 'amount', type: 'uint256' }],
|
|
1235
|
+
},
|
|
1236
|
+
primaryType: 'Message',
|
|
1237
|
+
message: { order: { amount: 1n } },
|
|
1238
|
+
}),
|
|
1239
|
+
).toMatchInlineSnapshot(
|
|
1240
|
+
`"0x97a137d1f96a18c6a214fd8e7b991ed7309ff25f223b68aa63a4670f9838a94a"`,
|
|
1241
|
+
)
|
|
1242
|
+
})
|
|
1057
1243
|
})
|
|
1058
1244
|
|
|
1059
1245
|
describe('hashDomain', () => {
|
|
@@ -1613,6 +1799,7 @@ test('exports', () => {
|
|
|
1613
1799
|
"InvalidDomainError",
|
|
1614
1800
|
"InvalidPrimaryTypeError",
|
|
1615
1801
|
"InvalidStructTypeError",
|
|
1802
|
+
"InvalidTypeError",
|
|
1616
1803
|
"InvalidArrayError",
|
|
1617
1804
|
"InvalidArrayLengthError",
|
|
1618
1805
|
"encodeData",
|
|
@@ -4,6 +4,7 @@ import type * as AbiItem from '../AbiItem.js'
|
|
|
4
4
|
import type * as AbiParameters from '../AbiParameters.js'
|
|
5
5
|
import * as Address from '../Address.js'
|
|
6
6
|
import * as Errors from '../Errors.js'
|
|
7
|
+
import * as Hex from '../Hex.js'
|
|
7
8
|
import { getArrayComponents } from './abiParameters.js'
|
|
8
9
|
import type {
|
|
9
10
|
Compute,
|
|
@@ -601,11 +602,32 @@ export function isArgOfType(
|
|
|
601
602
|
},
|
|
602
603
|
)
|
|
603
604
|
|
|
604
|
-
if (integerTypeRegex.test(abiParameterType))
|
|
605
|
-
|
|
605
|
+
if (integerTypeRegex.test(abiParameterType)) {
|
|
606
|
+
if (typeof arg !== 'bigint' && typeof arg !== 'number') return false
|
|
607
|
+
if (typeof arg === 'number' && !Number.isInteger(arg)) return false
|
|
606
608
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
+
// An overload whose integer range excludes the value does not match.
|
|
610
|
+
const signed = abiParameterType.startsWith('int')
|
|
611
|
+
const bits = Number.parseInt(
|
|
612
|
+
abiParameterType.replace(/^u?int/, '') || '256',
|
|
613
|
+
10,
|
|
614
|
+
)
|
|
615
|
+
const max = 2n ** (BigInt(bits) - (signed ? 1n : 0n)) - 1n
|
|
616
|
+
const min = signed ? -max - 1n : 0n
|
|
617
|
+
return arg >= min && arg <= max
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (fixedBytesTypeRegex.test(abiParameterType)) {
|
|
621
|
+
if (typeof arg !== 'string' && !(arg instanceof Uint8Array))
|
|
622
|
+
return false
|
|
623
|
+
// An overload whose fixed size differs from the value does not match.
|
|
624
|
+
const bytes = abiParameterType.slice(5)
|
|
625
|
+
if (!bytes) return true
|
|
626
|
+
if (arg instanceof Uint8Array)
|
|
627
|
+
return arg.length === Number.parseInt(bytes, 10)
|
|
628
|
+
if (!Hex.validate(arg)) return false
|
|
629
|
+
return Hex.size(arg as Hex.Hex) === Number.parseInt(bytes, 10)
|
|
630
|
+
}
|
|
609
631
|
|
|
610
632
|
// Fixed-length (`<type>[M]`) and dynamic (`<type>[]`) arrays.
|
|
611
633
|
const arrayComponents = getArrayComponents(abiParameterType)
|
|
@@ -683,6 +705,15 @@ export function getAmbiguousTypes(
|
|
|
683
705
|
[(args[parameterIndex] as readonly unknown[] | undefined)?.[0]],
|
|
684
706
|
)
|
|
685
707
|
if (types) return types
|
|
708
|
+
// Same element type, different array shape: `uint256[]` and `uint256[2]`
|
|
709
|
+
// both accept `[1n, 1n]`, but one is dynamic and the other is static.
|
|
710
|
+
// Skip this when the element types differ. `string[]` vs `address[]`
|
|
711
|
+
// stays unambiguous for a non-address string.
|
|
712
|
+
if (
|
|
713
|
+
sourceArray[1] === targetArray[1] &&
|
|
714
|
+
sourceParameter.type !== targetParameter.type
|
|
715
|
+
)
|
|
716
|
+
return [sourceParameter.type, targetParameter.type]
|
|
686
717
|
continue
|
|
687
718
|
}
|
|
688
719
|
|
|
@@ -690,20 +721,20 @@ export function getAmbiguousTypes(
|
|
|
690
721
|
const targetType = targetParameter.type
|
|
691
722
|
const arg = args[parameterIndex] as Address.Address
|
|
692
723
|
|
|
693
|
-
|
|
694
|
-
if (
|
|
695
|
-
(sourceType === 'address' && targetType === 'bytes20') ||
|
|
696
|
-
(sourceType === 'bytes20' && targetType === 'address')
|
|
697
|
-
)
|
|
698
|
-
ambiguous = true
|
|
699
|
-
else if (
|
|
700
|
-
(sourceType === 'address' && targetType === 'string') ||
|
|
701
|
-
(sourceType === 'string' && targetType === 'address') ||
|
|
702
|
-
(sourceType === 'address' && targetType === 'bytes') ||
|
|
703
|
-
(sourceType === 'bytes' && targetType === 'address')
|
|
704
|
-
)
|
|
724
|
+
const ambiguous = (() => {
|
|
705
725
|
// An `undefined` value comes from an empty array, which matches both types.
|
|
706
|
-
|
|
726
|
+
// This stays above the catch-all: a non-address string is not ambiguous.
|
|
727
|
+
if (
|
|
728
|
+
(sourceType === 'address' && targetType === 'string') ||
|
|
729
|
+
(sourceType === 'string' && targetType === 'address') ||
|
|
730
|
+
(sourceType === 'address' && targetType === 'bytes') ||
|
|
731
|
+
(sourceType === 'bytes' && targetType === 'address')
|
|
732
|
+
)
|
|
733
|
+
return arg === undefined || Address.validate(arg, { strict: false })
|
|
734
|
+
// Distinct types that both matched cannot be distinguished at runtime
|
|
735
|
+
// (e.g. `bytes` vs `bytes32`, `uint256` vs `int256`, `address` vs `bytes20`).
|
|
736
|
+
return sourceType !== targetType
|
|
737
|
+
})()
|
|
707
738
|
|
|
708
739
|
if (ambiguous) return [sourceType, targetType]
|
|
709
740
|
}
|
|
@@ -178,6 +178,14 @@ export function decodeAddress(
|
|
|
178
178
|
) {
|
|
179
179
|
const { checksum = false } = options
|
|
180
180
|
const value = cursor.readBytes(32)
|
|
181
|
+
// An address occupies the low 20 bytes of the word; the high 12 bytes
|
|
182
|
+
// must be zero padding.
|
|
183
|
+
for (let i = 0; i < 12; i++)
|
|
184
|
+
if (value[i] !== 0)
|
|
185
|
+
throw new Address.InvalidAddressError({
|
|
186
|
+
address: Hex.fromBytes(value),
|
|
187
|
+
cause: new Address.InvalidInputError(),
|
|
188
|
+
})
|
|
181
189
|
const wrap = (address: Hex.Hex) =>
|
|
182
190
|
checksum ? Address.checksum(address) : address
|
|
183
191
|
return [wrap(Hex.fromBytes(Bytes.slice(value, -20))), 32]
|
|
@@ -185,6 +193,7 @@ export function decodeAddress(
|
|
|
185
193
|
|
|
186
194
|
export declare namespace decodeAddress {
|
|
187
195
|
type ErrorType =
|
|
196
|
+
| Address.InvalidAddressError
|
|
188
197
|
| Hex.fromBytes.ErrorType
|
|
189
198
|
| Bytes.slice.ErrorType
|
|
190
199
|
| Errors.GlobalErrorType
|
|
@@ -356,19 +365,28 @@ export function decodeNumber(
|
|
|
356
365
|
) {
|
|
357
366
|
const signed = param.type.startsWith('int')
|
|
358
367
|
const size = Number.parseInt(param.type.split('int')[1] || '256', 10)
|
|
359
|
-
const value = cursor.readBytes(32)
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
368
|
+
const value = Bytes.toBigInt(cursor.readBytes(32), { signed })
|
|
369
|
+
// The word must hold a value that fits the declared size (zero-padded for
|
|
370
|
+
// unsigned, sign-extended for signed types).
|
|
371
|
+
if (size < 256) {
|
|
372
|
+
const max = (1n << BigInt(signed ? size - 1 : size)) - 1n
|
|
373
|
+
const min = signed ? -max - 1n : 0n
|
|
374
|
+
if (value > max || value < min)
|
|
375
|
+
throw new Hex.IntegerOutOfRangeError({
|
|
376
|
+
max: max.toString(),
|
|
377
|
+
min: min.toString(),
|
|
378
|
+
signed,
|
|
379
|
+
size: size / 8,
|
|
380
|
+
value: value.toString(),
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
return [size > 48 ? value : Number(value), 32]
|
|
366
384
|
}
|
|
367
385
|
|
|
368
386
|
export declare namespace decodeNumber {
|
|
369
387
|
type ErrorType =
|
|
370
|
-
| Bytes.toNumber.ErrorType
|
|
371
388
|
| Bytes.toBigInt.ErrorType
|
|
389
|
+
| Hex.IntegerOutOfRangeError
|
|
372
390
|
| Errors.GlobalErrorType
|
|
373
391
|
}
|
|
374
392
|
|
|
@@ -13,6 +13,7 @@ import type * as Transaction from '../../Transaction.js'
|
|
|
13
13
|
import type * as TransactionReceipt from '../../TransactionReceipt.js'
|
|
14
14
|
import type * as TransactionRequest from '../../TransactionRequest.js'
|
|
15
15
|
import type * as TxEnvelope from '../../TxEnvelope.js'
|
|
16
|
+
import type { OneOf, UnionCompute } from '../types.js'
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Union of all JSON-RPC Methods for the `eth_` namespace.
|
|
@@ -237,7 +238,7 @@ export type Eth = RpcSchema.From<
|
|
|
237
238
|
ReturnType: {
|
|
238
239
|
capabilities?: Record<string, unknown> | undefined
|
|
239
240
|
raw?: Hex.Hex | undefined
|
|
240
|
-
tx: TxEnvelope.Rpc & { from: Address.Address }
|
|
241
|
+
tx: UnionCompute<OneOf<TxEnvelope.Rpc> & { from: Address.Address }>
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
/**
|
|
@@ -97,8 +97,8 @@ const signature_keychain = {
|
|
|
97
97
|
|
|
98
98
|
describe('configurable account binding', () => {
|
|
99
99
|
test('behavior: accepts unbound configurable grants', () => {
|
|
100
|
-
// The node binds a
|
|
101
|
-
//
|
|
100
|
+
// The node binds a grant to its signer, so `account` is optional. From T14
|
|
101
|
+
// only the root key or owner quorum may sign a grant (TIP-1137).
|
|
102
102
|
const signed = KeyAuthorization.from(
|
|
103
103
|
{ address, chainId: 1n, type: 'configurable' },
|
|
104
104
|
{ signature: signature_secp256k1 },
|
|
@@ -2673,7 +2673,9 @@ describe('witness (TIP-1053)', () => {
|
|
|
2673
2673
|
})
|
|
2674
2674
|
})
|
|
2675
2675
|
|
|
2676
|
-
|
|
2676
|
+
// TIP-1137 removes admin keys at T14, but the wire fields are unchanged, so
|
|
2677
|
+
// pre-T14 history (T6 to T13) must still encode, decode, and hash.
|
|
2678
|
+
describe('historical admin keys (TIP-1049)', () => {
|
|
2677
2679
|
const account = '0x1111111111111111111111111111111111111111' as const
|
|
2678
2680
|
|
|
2679
2681
|
test('from: preserves isAdmin and account', () => {
|
|
@@ -2858,7 +2860,7 @@ describe('admin keys (TIP-1049)', () => {
|
|
|
2858
2860
|
})
|
|
2859
2861
|
|
|
2860
2862
|
describe('configurable admin keys', () => {
|
|
2861
|
-
test('behavior: a configurable
|
|
2863
|
+
test('behavior: round-trips a pre-T14 configurable admin grant', () => {
|
|
2862
2864
|
const signed = KeyAuthorization.from(
|
|
2863
2865
|
{
|
|
2864
2866
|
account: signature_configurable.account,
|
|
@@ -38,7 +38,10 @@ export type KeyAuthorization<
|
|
|
38
38
|
bigintType = bigint,
|
|
39
39
|
numberType = number,
|
|
40
40
|
> = {
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Account address this authorization is bound to. When present, the Tempo node
|
|
43
|
+
* requires it to equal the transaction sender.
|
|
44
|
+
*/
|
|
42
45
|
account?: Address.Address | undefined
|
|
43
46
|
/** Address derived from the public key of the key type. */
|
|
44
47
|
address: Address.Address
|
|
@@ -46,7 +49,14 @@ export type KeyAuthorization<
|
|
|
46
49
|
chainId: bigintType
|
|
47
50
|
/** Unix timestamp when key expires (undefined = never expires). */
|
|
48
51
|
expiry?: numberType | null | undefined
|
|
49
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Whether this authorization provisions an admin access key (TIP-1049).
|
|
54
|
+
*
|
|
55
|
+
* @deprecated Admin access keys are removed at T14 by
|
|
56
|
+
* [TIP-1137](https://docs.tempo.xyz/protocol/tips/tip-1137). From T14 the Tempo
|
|
57
|
+
* node rejects any key authorization with `isAdmin: true`. The field is still
|
|
58
|
+
* encoded and decoded so pre-T14 history round-trips.
|
|
59
|
+
*/
|
|
50
60
|
isAdmin?: boolean | undefined
|
|
51
61
|
/**
|
|
52
62
|
* TIP20 spending limits for this key.
|
|
@@ -104,7 +114,7 @@ export type Input = KeyAuthorization<false, bigint, number>
|
|
|
104
114
|
|
|
105
115
|
/** RPC representation matching the node's wire format. */
|
|
106
116
|
export type Rpc = {
|
|
107
|
-
/** Optional account address binding
|
|
117
|
+
/** Optional account address binding. When present, must equal the transaction sender. */
|
|
108
118
|
account?: Address.Address | null | undefined
|
|
109
119
|
/** Allowed call scopes (node field: `allowedCalls`). */
|
|
110
120
|
allowedCalls?: readonly RpcCallScope[] | null | undefined
|
|
@@ -112,7 +122,12 @@ export type Rpc = {
|
|
|
112
122
|
chainId: Hex.Hex
|
|
113
123
|
/** Expiry timestamp (hex quantity or null). */
|
|
114
124
|
expiry: Hex.Hex | null | undefined
|
|
115
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* Whether this authorization provisions an admin access key (TIP-1049).
|
|
127
|
+
*
|
|
128
|
+
* @deprecated Rejected from T14 by [TIP-1137](https://docs.tempo.xyz/protocol/tips/tip-1137).
|
|
129
|
+
* Still decoded for pre-T14 history.
|
|
130
|
+
*/
|
|
116
131
|
isAdmin?: boolean | null | undefined
|
|
117
132
|
/** Key identifier. */
|
|
118
133
|
keyId: Address.Address
|
|
@@ -712,7 +727,9 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
712
727
|
const isAdmin = (() => {
|
|
713
728
|
if (isAbsent(rawIsAdmin)) return undefined
|
|
714
729
|
// TIP-1049: the admin marker is strictly `0x01`. Any other value is a
|
|
715
|
-
// protocol-level decode error on the node, so reject it here too.
|
|
730
|
+
// protocol-level decode error on the node, so reject it here too. T14
|
|
731
|
+
// (TIP-1137) rejects admin authorizations at validation, not decoding,
|
|
732
|
+
// so pre-T14 history still decodes.
|
|
716
733
|
if (rawIsAdmin !== '0x01')
|
|
717
734
|
throw new InvalidAdminMarkerError(rawIsAdmin as Hex.Hex)
|
|
718
735
|
return true
|
|
@@ -1140,8 +1157,9 @@ export function toTuple<const authorization extends KeyAuthorization>(
|
|
|
1140
1157
|
{ value: witness, placeholder: '0x' },
|
|
1141
1158
|
// TIP-1049: admin marker. Present = `0x01` (RLP integer 1); absent
|
|
1142
1159
|
// skipped or omitted. Any other value is a hard decode error on the node.
|
|
1160
|
+
// TIP-1137 keeps this encoding but rejects `0x01` from T14.
|
|
1143
1161
|
{ value: isAdmin ? '0x01' : undefined, placeholder: '0x' },
|
|
1144
|
-
//
|
|
1162
|
+
// Optional account binding. Last field — never a placeholder.
|
|
1145
1163
|
{ value: account, placeholder: '0x' },
|
|
1146
1164
|
]
|
|
1147
1165
|
let lastPresent = -1
|
|
@@ -1245,8 +1263,8 @@ export class InvalidSignatureTypeError extends Error {
|
|
|
1245
1263
|
/**
|
|
1246
1264
|
* Thrown when a configurable account key grant omits its parent account binding.
|
|
1247
1265
|
*
|
|
1248
|
-
* @deprecated No longer thrown. The Tempo node
|
|
1249
|
-
*
|
|
1266
|
+
* @deprecated No longer thrown. The Tempo node does not require `account`; when
|
|
1267
|
+
* present, it must equal the transaction sender.
|
|
1250
1268
|
*/
|
|
1251
1269
|
export class MissingAccountError extends Error {
|
|
1252
1270
|
override readonly name = 'KeyAuthorization.MissingAccountError'
|
|
@@ -106,6 +106,19 @@ describe('assert', () => {
|
|
|
106
106
|
)
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
+
test('tip above zero fee cap', () => {
|
|
110
|
+
expect(() =>
|
|
111
|
+
TxEnvelopeTempo.assert({
|
|
112
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
113
|
+
chainId: 1,
|
|
114
|
+
maxFeePerGas: 0n,
|
|
115
|
+
maxPriorityFeePerGas: 1n,
|
|
116
|
+
}),
|
|
117
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
118
|
+
`[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
|
|
109
122
|
test('invalid chain id', () => {
|
|
110
123
|
expect(() =>
|
|
111
124
|
TxEnvelopeTempo.assert({
|
|
@@ -138,6 +151,19 @@ describe('deserialize', () => {
|
|
|
138
151
|
expect(deserialized).toEqual(transaction)
|
|
139
152
|
})
|
|
140
153
|
|
|
154
|
+
test('tip above empty fee cap', () => {
|
|
155
|
+
const serialized = TxEnvelopeTempo.serialize({
|
|
156
|
+
calls: [{ to: '0x0000000000000000000000000000000000000000' }],
|
|
157
|
+
chainId: 1,
|
|
158
|
+
maxPriorityFeePerGas: 1n,
|
|
159
|
+
})
|
|
160
|
+
expect(() =>
|
|
161
|
+
TxEnvelopeTempo.deserialize(serialized),
|
|
162
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
163
|
+
`[TransactionEnvelope.TipAboveFeeCapError: The provided tip (\`maxPriorityFeePerGas\` = 0.000000001 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 0 gwei).]`,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
|
|
141
167
|
test('fee payer', () => {
|
|
142
168
|
const serialized = TxEnvelopeTempo.serialize(transaction, {
|
|
143
169
|
format: 'feePayer',
|
|
@@ -214,8 +214,8 @@ export function assert(envelope: PartialBy<TxEnvelopeTempo, 'type'>) {
|
|
|
214
214
|
})
|
|
215
215
|
|
|
216
216
|
if (
|
|
217
|
-
maxPriorityFeePerGas &&
|
|
218
|
-
maxFeePerGas &&
|
|
217
|
+
typeof maxPriorityFeePerGas === 'bigint' &&
|
|
218
|
+
typeof maxFeePerGas === 'bigint' &&
|
|
219
219
|
maxPriorityFeePerGas > maxFeePerGas
|
|
220
220
|
)
|
|
221
221
|
throw new TransactionEnvelope.TipAboveFeeCapError({
|
|
@@ -335,6 +335,12 @@ export function deserialize(serialized: Serialized): Compute<TxEnvelopeTempo> {
|
|
|
335
335
|
transaction.maxFeePerGas = BigInt(maxFeePerGas)
|
|
336
336
|
if (Hex.validate(maxPriorityFeePerGas) && maxPriorityFeePerGas !== '0x')
|
|
337
337
|
transaction.maxPriorityFeePerGas = BigInt(maxPriorityFeePerGas)
|
|
338
|
+
// An empty `maxFeePerGas` encodes zero, so a tip above it is rejected.
|
|
339
|
+
if (
|
|
340
|
+
transaction.maxPriorityFeePerGas &&
|
|
341
|
+
typeof transaction.maxFeePerGas === 'undefined'
|
|
342
|
+
)
|
|
343
|
+
transaction.maxFeePerGas = 0n
|
|
338
344
|
if (Hex.validate(nonceKey))
|
|
339
345
|
transaction.nonceKey = nonceKey === '0x' ? 0n : BigInt(nonceKey)
|
|
340
346
|
if (Hex.validate(validBefore) && validBefore !== '0x')
|