ox 0.14.11 → 0.14.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 +14 -0
- package/_cjs/erc8021/Attribution.js +7 -1
- package/_cjs/erc8021/Attribution.js.map +1 -1
- package/_cjs/tempo/KeyAuthorization.js +150 -21
- package/_cjs/tempo/KeyAuthorization.js.map +1 -1
- package/_cjs/tempo/Period.js +31 -0
- package/_cjs/tempo/Period.js.map +1 -0
- package/_cjs/tempo/index.js +2 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/version.js +1 -1
- package/_esm/erc8021/Attribution.js +7 -1
- package/_esm/erc8021/Attribution.js.map +1 -1
- package/_esm/tempo/KeyAuthorization.js +159 -22
- package/_esm/tempo/KeyAuthorization.js.map +1 -1
- package/_esm/tempo/Period.js +92 -0
- package/_esm/tempo/Period.js.map +1 -0
- package/_esm/tempo/index.js +29 -0
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/version.js +1 -1
- package/_types/erc8021/Attribution.d.ts +2 -0
- package/_types/erc8021/Attribution.d.ts.map +1 -1
- package/_types/tempo/KeyAuthorization.d.ts +95 -19
- package/_types/tempo/KeyAuthorization.d.ts.map +1 -1
- package/_types/tempo/Period.d.ts +78 -0
- package/_types/tempo/Period.d.ts.map +1 -0
- package/_types/tempo/index.d.ts +29 -0
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/version.d.ts +1 -1
- package/erc8021/Attribution.ts +12 -1
- package/package.json +6 -1
- package/tempo/KeyAuthorization.test.ts +407 -3
- package/tempo/KeyAuthorization.ts +291 -51
- package/tempo/Period/package.json +6 -0
- package/tempo/Period.test.ts +44 -0
- package/tempo/Period.ts +97 -0
- package/tempo/e2e.test.ts +969 -1
- package/tempo/index.ts +30 -0
- package/version.ts +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as AbiItem from '../core/AbiItem.js'
|
|
1
2
|
import type * as Address from '../core/Address.js'
|
|
2
3
|
import type * as Errors from '../core/Errors.js'
|
|
3
4
|
import * as Hash from '../core/Hash.js'
|
|
@@ -41,7 +42,17 @@ export type KeyAuthorization<
|
|
|
41
42
|
/** Unix timestamp when key expires (undefined = never expires). */
|
|
42
43
|
expiry?: numberType | null | undefined
|
|
43
44
|
/** TIP20 spending limits for this key. */
|
|
44
|
-
limits?:
|
|
45
|
+
limits?:
|
|
46
|
+
| readonly TokenLimit<bigintType, numberType, addressType>[]
|
|
47
|
+
| undefined
|
|
48
|
+
/**
|
|
49
|
+
* Call scopes restricting which contracts/selectors this key can call.
|
|
50
|
+
*
|
|
51
|
+
* - `undefined` = unrestricted key (any call allowed)
|
|
52
|
+
* - `[]` = scoped mode with no calls allowed
|
|
53
|
+
* - `[...]` = only listed contract+selector combinations allowed
|
|
54
|
+
*/
|
|
55
|
+
scopes?: readonly Scope<addressType>[] | undefined
|
|
45
56
|
/** Key type. (secp256k1, P256, WebAuthn). */
|
|
46
57
|
type: SignatureEnvelope.Type
|
|
47
58
|
} & (signed extends true
|
|
@@ -60,16 +71,43 @@ export type Input = KeyAuthorization<
|
|
|
60
71
|
TempoAddress.Address
|
|
61
72
|
>
|
|
62
73
|
|
|
63
|
-
/** RPC representation
|
|
64
|
-
export type Rpc =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
/** RPC representation matching the node's wire format. */
|
|
75
|
+
export type Rpc = {
|
|
76
|
+
/** Allowed call scopes (node field: `allowedCalls`). */
|
|
77
|
+
allowedCalls?: readonly RpcCallScope[] | undefined
|
|
78
|
+
/** Chain ID (hex quantity). */
|
|
79
|
+
chainId: Hex.Hex
|
|
80
|
+
/** Expiry timestamp (hex quantity or null). */
|
|
81
|
+
expiry: Hex.Hex | null | undefined
|
|
82
|
+
/** Key identifier. */
|
|
68
83
|
keyId: Address.Address
|
|
84
|
+
/** Key type. */
|
|
69
85
|
keyType: SignatureEnvelope.Type
|
|
86
|
+
/** Token spending limits. */
|
|
87
|
+
limits?: readonly RpcTokenLimit[] | undefined
|
|
88
|
+
/** Signature envelope. */
|
|
70
89
|
signature: SignatureEnvelope.SignatureEnvelopeRpc
|
|
71
90
|
}
|
|
72
91
|
|
|
92
|
+
/** RPC representation of a token limit (matches node's `TokenLimit` serde). */
|
|
93
|
+
export type RpcTokenLimit = {
|
|
94
|
+
token: Address.Address
|
|
95
|
+
limit: Hex.Hex
|
|
96
|
+
period?: Hex.Hex | undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** RPC representation of a call scope (matches node's `CallScope` serde). */
|
|
100
|
+
export type RpcCallScope = {
|
|
101
|
+
target: Address.Address
|
|
102
|
+
selectorRules?: readonly RpcSelectorRule[]
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** RPC representation of a selector rule (matches node's `SelectorRule` serde). */
|
|
106
|
+
export type RpcSelectorRule = {
|
|
107
|
+
selector: Hex.Hex
|
|
108
|
+
recipients?: readonly Address.Address[]
|
|
109
|
+
}
|
|
110
|
+
|
|
73
111
|
/** Signed representation of a Key Authorization. */
|
|
74
112
|
export type Signed<
|
|
75
113
|
bigintType = bigint,
|
|
@@ -83,29 +121,68 @@ type BaseTuple = readonly [
|
|
|
83
121
|
keyId: Address.Address,
|
|
84
122
|
]
|
|
85
123
|
|
|
124
|
+
type TokenLimitTuple =
|
|
125
|
+
| readonly [token: Address.Address, limit: Hex.Hex]
|
|
126
|
+
| readonly [token: Address.Address, limit: Hex.Hex, period: Hex.Hex]
|
|
127
|
+
|
|
128
|
+
type SelectorRuleTuple = readonly [
|
|
129
|
+
selector: Hex.Hex,
|
|
130
|
+
recipients: readonly Address.Address[],
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
type CallScopeTuple = readonly [
|
|
134
|
+
target: Address.Address,
|
|
135
|
+
selectorRules: readonly SelectorRuleTuple[],
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
type AuthorizationTuple =
|
|
139
|
+
| BaseTuple
|
|
140
|
+
| readonly [...BaseTuple, expiry: Hex.Hex]
|
|
141
|
+
| readonly [...BaseTuple, expiry: Hex.Hex, limits: readonly TokenLimitTuple[]]
|
|
142
|
+
| readonly [
|
|
143
|
+
...BaseTuple,
|
|
144
|
+
expiry: Hex.Hex,
|
|
145
|
+
limits: readonly TokenLimitTuple[],
|
|
146
|
+
calls: readonly CallScopeTuple[],
|
|
147
|
+
]
|
|
148
|
+
|
|
86
149
|
/** Tuple representation of a Key Authorization. */
|
|
87
150
|
export type Tuple<signed extends boolean = boolean> = signed extends true
|
|
88
|
-
? readonly [
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
151
|
+
? readonly [authorization: AuthorizationTuple, signature: Hex.Hex]
|
|
152
|
+
: readonly [authorization: AuthorizationTuple]
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Call scope entry restricting which contract, selector, and recipients an access key can use.
|
|
156
|
+
*
|
|
157
|
+
* Multiple entries with the same `address` are grouped by target on the wire.
|
|
158
|
+
*
|
|
159
|
+
* - `{ address }` = any selector on this contract
|
|
160
|
+
* - `{ address, selector }` = specific selector
|
|
161
|
+
* - `{ address, selector, recipients }` = selector + recipient constraint
|
|
162
|
+
*
|
|
163
|
+
* [TIP-1011 Specification](https://docs.tempo.xyz/protocol/transactions/tip-1011)
|
|
164
|
+
*/
|
|
165
|
+
export type Scope<addressType = Address.Address> = {
|
|
166
|
+
/** Target contract address. */
|
|
167
|
+
address: addressType
|
|
168
|
+
/**
|
|
169
|
+
* 4-byte function selector, or a human-readable ABI signature
|
|
170
|
+
* (e.g. `'transfer(address,uint256)'` or `'function transfer(address,uint256)'`).
|
|
171
|
+
*
|
|
172
|
+
* Signatures are encoded into a 4-byte selector automatically.
|
|
173
|
+
* Omit to allow any selector on this contract.
|
|
174
|
+
*/
|
|
175
|
+
selector?: Hex.Hex | string | undefined
|
|
176
|
+
/**
|
|
177
|
+
* Recipient allowlist for this selector (first ABI `address` argument).
|
|
178
|
+
*
|
|
179
|
+
* - `undefined` or `[]` = any recipient allowed
|
|
180
|
+
* - `[...]` = only listed recipients allowed
|
|
181
|
+
*
|
|
182
|
+
* Only valid for constrained selectors: `transfer`, `approve`, `transferWithMemo`.
|
|
183
|
+
*/
|
|
184
|
+
recipients?: readonly addressType[] | undefined
|
|
185
|
+
}
|
|
109
186
|
|
|
110
187
|
/**
|
|
111
188
|
* Token spending limit for access keys.
|
|
@@ -115,11 +192,22 @@ export type Tuple<signed extends boolean = boolean> = signed extends true
|
|
|
115
192
|
*
|
|
116
193
|
* [Access Keys Specification](https://docs.tempo.xyz/protocol/transactions/spec-tempo-transaction#access-keys)
|
|
117
194
|
*/
|
|
118
|
-
export type TokenLimit<
|
|
195
|
+
export type TokenLimit<
|
|
196
|
+
bigintType = bigint,
|
|
197
|
+
numberType = number,
|
|
198
|
+
addressType = Address.Address,
|
|
199
|
+
> = {
|
|
119
200
|
/** Address of the TIP-20 token. */
|
|
120
201
|
token: addressType
|
|
121
|
-
/** Maximum spending amount for this token (enforced over the key's lifetime). */
|
|
202
|
+
/** Maximum spending amount for this token (enforced over the key's lifetime, or per period if `period` \> 0). */
|
|
122
203
|
limit: bigintType
|
|
204
|
+
/**
|
|
205
|
+
* Period duration in seconds for recurring spending limits.
|
|
206
|
+
*
|
|
207
|
+
* - `0` or `undefined` = one-time limit
|
|
208
|
+
* - `\> 0` = periodic limit that resets every `period` seconds
|
|
209
|
+
*/
|
|
210
|
+
period?: numberType | undefined
|
|
123
211
|
}
|
|
124
212
|
|
|
125
213
|
/**
|
|
@@ -268,6 +356,11 @@ export function from<
|
|
|
268
356
|
if ('keyId' in authorization) return fromRpc(authorization as Rpc) as never
|
|
269
357
|
const auth = authorization as KeyAuthorization & {
|
|
270
358
|
limits?: readonly { token: TempoAddress.Address; limit: bigint }[]
|
|
359
|
+
scopes?: readonly {
|
|
360
|
+
address: TempoAddress.Address
|
|
361
|
+
selector?: Hex.Hex | string
|
|
362
|
+
recipients?: readonly TempoAddress.Address[]
|
|
363
|
+
}[]
|
|
271
364
|
}
|
|
272
365
|
const resolved = {
|
|
273
366
|
...auth,
|
|
@@ -280,6 +373,22 @@ export function from<
|
|
|
280
373
|
})),
|
|
281
374
|
}
|
|
282
375
|
: {}),
|
|
376
|
+
...(auth.scopes
|
|
377
|
+
? {
|
|
378
|
+
scopes: auth.scopes.map((scope) => ({
|
|
379
|
+
...scope,
|
|
380
|
+
address: TempoAddress.resolve(scope.address),
|
|
381
|
+
selector: resolveSelector(scope.selector),
|
|
382
|
+
...(scope.recipients
|
|
383
|
+
? {
|
|
384
|
+
recipients: scope.recipients.map((r) =>
|
|
385
|
+
TempoAddress.resolve(r),
|
|
386
|
+
),
|
|
387
|
+
}
|
|
388
|
+
: {}),
|
|
389
|
+
})),
|
|
390
|
+
}
|
|
391
|
+
: {}),
|
|
283
392
|
}
|
|
284
393
|
if (options.signature)
|
|
285
394
|
return {
|
|
@@ -344,8 +453,27 @@ export declare namespace from {
|
|
|
344
453
|
* @returns A signed {@link ox#AuthorizationTempo.AuthorizationTempo}.
|
|
345
454
|
*/
|
|
346
455
|
export function fromRpc(authorization: Rpc): Signed {
|
|
347
|
-
const { chainId, keyId, expiry, limits, keyType } =
|
|
456
|
+
const { allowedCalls, chainId, keyId, expiry, limits, keyType } =
|
|
457
|
+
authorization
|
|
348
458
|
const signature = SignatureEnvelope.fromRpc(authorization.signature)
|
|
459
|
+
|
|
460
|
+
// Unflatten nested allowedCalls into flat scopes
|
|
461
|
+
const scopes = allowedCalls
|
|
462
|
+
? allowedCalls.flatMap((callScope) => {
|
|
463
|
+
if (!callScope.selectorRules || callScope.selectorRules.length === 0)
|
|
464
|
+
return [{ address: callScope.target }] as Scope[]
|
|
465
|
+
return callScope.selectorRules.map(
|
|
466
|
+
(rule): Scope => ({
|
|
467
|
+
address: callScope.target,
|
|
468
|
+
selector: normalizeSelector(rule.selector),
|
|
469
|
+
...(rule.recipients && rule.recipients.length > 0
|
|
470
|
+
? { recipients: rule.recipients }
|
|
471
|
+
: {}),
|
|
472
|
+
}),
|
|
473
|
+
)
|
|
474
|
+
})
|
|
475
|
+
: undefined
|
|
476
|
+
|
|
349
477
|
return {
|
|
350
478
|
address: keyId,
|
|
351
479
|
chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
|
|
@@ -353,7 +481,11 @@ export function fromRpc(authorization: Rpc): Signed {
|
|
|
353
481
|
limits: limits?.map((limit) => ({
|
|
354
482
|
token: limit.token,
|
|
355
483
|
limit: BigInt(limit.limit),
|
|
484
|
+
...(limit.period && hexToNumber(limit.period) > 0
|
|
485
|
+
? { period: hexToNumber(limit.period) }
|
|
486
|
+
: {}),
|
|
356
487
|
})),
|
|
488
|
+
...(scopes ? { scopes } : {}),
|
|
357
489
|
signature,
|
|
358
490
|
type: keyType,
|
|
359
491
|
}
|
|
@@ -406,7 +538,7 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
406
538
|
tuple: tuple,
|
|
407
539
|
): fromTuple.ReturnType<tuple> {
|
|
408
540
|
const [authorization, signatureSerialized] = tuple
|
|
409
|
-
const [chainId, keyType_hex, keyId, expiry, limits] = authorization
|
|
541
|
+
const [chainId, keyType_hex, keyId, expiry, limits, scopes] = authorization
|
|
410
542
|
const keyType = (() => {
|
|
411
543
|
switch (keyType_hex) {
|
|
412
544
|
case '0x':
|
|
@@ -422,16 +554,50 @@ export function fromTuple<const tuple extends Tuple>(
|
|
|
422
554
|
})()
|
|
423
555
|
const args: KeyAuthorization = {
|
|
424
556
|
address: keyId,
|
|
425
|
-
expiry:
|
|
557
|
+
expiry:
|
|
558
|
+
typeof expiry !== 'undefined'
|
|
559
|
+
? hexToNumber(expiry) || undefined
|
|
560
|
+
: undefined,
|
|
426
561
|
type: keyType,
|
|
427
562
|
chainId: chainId === '0x' ? 0n : Hex.toBigInt(chainId),
|
|
428
|
-
...(typeof expiry !== 'undefined'
|
|
429
|
-
|
|
563
|
+
...(typeof expiry !== 'undefined'
|
|
564
|
+
? { expiry: hexToNumber(expiry) || undefined }
|
|
565
|
+
: {}),
|
|
566
|
+
...(typeof limits !== 'undefined' &&
|
|
567
|
+
Array.isArray(limits) &&
|
|
568
|
+
limits.length > 0
|
|
430
569
|
? {
|
|
431
|
-
limits: limits.map((
|
|
432
|
-
token,
|
|
433
|
-
|
|
434
|
-
|
|
570
|
+
limits: limits.map((limitTuple: any) => {
|
|
571
|
+
const [token, limit, period] = limitTuple
|
|
572
|
+
return {
|
|
573
|
+
token,
|
|
574
|
+
limit: hexToBigint(limit),
|
|
575
|
+
...(typeof period !== 'undefined'
|
|
576
|
+
? { period: hexToNumber(period) }
|
|
577
|
+
: {}),
|
|
578
|
+
}
|
|
579
|
+
}),
|
|
580
|
+
}
|
|
581
|
+
: {}),
|
|
582
|
+
...(typeof scopes !== 'undefined' && Array.isArray(scopes)
|
|
583
|
+
? {
|
|
584
|
+
scopes: scopes.flatMap((scopeTuple: any) => {
|
|
585
|
+
const [address, selectorRules] = scopeTuple
|
|
586
|
+
// If no selector rules, this is an address-only scope
|
|
587
|
+
if (!Array.isArray(selectorRules) || selectorRules.length === 0)
|
|
588
|
+
return [{ address }]
|
|
589
|
+
// Flatten each selector rule into a separate scope entry
|
|
590
|
+
return selectorRules.map((ruleTuple: any) => {
|
|
591
|
+
const [selector, recipients] = ruleTuple
|
|
592
|
+
return {
|
|
593
|
+
address,
|
|
594
|
+
selector,
|
|
595
|
+
...(Array.isArray(recipients) && recipients.length > 0
|
|
596
|
+
? { recipients }
|
|
597
|
+
: {}),
|
|
598
|
+
}
|
|
599
|
+
})
|
|
600
|
+
}),
|
|
435
601
|
}
|
|
436
602
|
: {}),
|
|
437
603
|
}
|
|
@@ -637,18 +803,45 @@ export declare namespace serialize {
|
|
|
637
803
|
* @returns An RPC-formatted Key Authorization.
|
|
638
804
|
*/
|
|
639
805
|
export function toRpc(authorization: Signed): Rpc {
|
|
640
|
-
const { address, chainId, expiry, limits, type, signature } =
|
|
806
|
+
const { address, scopes, chainId, expiry, limits, type, signature } =
|
|
807
|
+
authorization
|
|
808
|
+
|
|
809
|
+
// Group flat scopes by address into nested allowedCalls wire format
|
|
810
|
+
const allowedCalls = (() => {
|
|
811
|
+
if (!scopes) return undefined
|
|
812
|
+
const grouped = new Map<string, RpcSelectorRule[]>()
|
|
813
|
+
for (const scope of scopes) {
|
|
814
|
+
const key = scope.address as string
|
|
815
|
+
if (!grouped.has(key)) grouped.set(key, [])
|
|
816
|
+
if (scope.selector) {
|
|
817
|
+
grouped.get(key)!.push({
|
|
818
|
+
selector: resolveSelector(scope.selector)!,
|
|
819
|
+
...(scope.recipients && scope.recipients.length > 0
|
|
820
|
+
? { recipients: scope.recipients }
|
|
821
|
+
: {}),
|
|
822
|
+
})
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return [...grouped.entries()].map(
|
|
826
|
+
([target, selectorRules]): RpcCallScope => ({
|
|
827
|
+
target: target as Address.Address,
|
|
828
|
+
...(selectorRules.length > 0 ? { selectorRules } : {}),
|
|
829
|
+
}),
|
|
830
|
+
)
|
|
831
|
+
})()
|
|
641
832
|
|
|
642
833
|
return {
|
|
643
834
|
chainId: chainId === 0n ? '0x' : Hex.fromNumber(chainId),
|
|
644
835
|
expiry: typeof expiry === 'number' ? Hex.fromNumber(expiry) : null,
|
|
645
|
-
|
|
836
|
+
keyId: TempoAddress.resolve(address),
|
|
837
|
+
keyType: type,
|
|
838
|
+
limits: limits?.map(({ token, limit, period }) => ({
|
|
646
839
|
token,
|
|
647
840
|
limit: Hex.fromNumber(limit),
|
|
841
|
+
...(period ? { period: numberToHex(period) } : {}),
|
|
648
842
|
})),
|
|
649
|
-
keyId: TempoAddress.resolve(address),
|
|
650
843
|
signature: SignatureEnvelope.toRpc(signature),
|
|
651
|
-
|
|
844
|
+
...(allowedCalls ? { allowedCalls } : {}),
|
|
652
845
|
}
|
|
653
846
|
}
|
|
654
847
|
|
|
@@ -690,7 +883,7 @@ export declare namespace toRpc {
|
|
|
690
883
|
export function toTuple<const authorization extends KeyAuthorization>(
|
|
691
884
|
authorization: authorization,
|
|
692
885
|
): toTuple.ReturnType<authorization> {
|
|
693
|
-
const { address, chainId, expiry, limits } = authorization
|
|
886
|
+
const { address, chainId, scopes, expiry, limits } = authorization
|
|
694
887
|
const signature = authorization.signature
|
|
695
888
|
? SignatureEnvelope.serialize(authorization.signature)
|
|
696
889
|
: undefined
|
|
@@ -706,20 +899,52 @@ export function toTuple<const authorization extends KeyAuthorization>(
|
|
|
706
899
|
throw new Error(`Invalid key type: ${authorization.type}`)
|
|
707
900
|
}
|
|
708
901
|
})()
|
|
709
|
-
const limitsValue = limits?.map((limit) =>
|
|
710
|
-
limit.token,
|
|
711
|
-
|
|
712
|
-
|
|
902
|
+
const limitsValue = limits?.map((limit) => {
|
|
903
|
+
const tuple: any[] = [limit.token, bigintToHex(limit.limit)]
|
|
904
|
+
// Canonical: omit period when 0 (one-time limit)
|
|
905
|
+
if (limit.period && limit.period > 0) tuple.push(numberToHex(limit.period))
|
|
906
|
+
return tuple
|
|
907
|
+
})
|
|
908
|
+
// Group flat scopes by address for wire format
|
|
909
|
+
const callsValue = (() => {
|
|
910
|
+
if (!scopes) return undefined
|
|
911
|
+
const grouped = new Map<
|
|
912
|
+
string,
|
|
913
|
+
[Hex.Hex, (readonly Address.Address[])[]][]
|
|
914
|
+
>()
|
|
915
|
+
for (const scope of scopes) {
|
|
916
|
+
const key = scope.address as string
|
|
917
|
+
if (!grouped.has(key)) grouped.set(key, [])
|
|
918
|
+
if (scope.selector) {
|
|
919
|
+
grouped
|
|
920
|
+
.get(key)!
|
|
921
|
+
.push([
|
|
922
|
+
resolveSelector(scope.selector)!,
|
|
923
|
+
(scope.recipients ??
|
|
924
|
+
[]) as unknown as (readonly Address.Address[])[],
|
|
925
|
+
])
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
return [...grouped.entries()].map(([address, selectorRules]) => [
|
|
929
|
+
address,
|
|
930
|
+
selectorRules.map(([selector, recipients]) => [selector, recipients]),
|
|
931
|
+
])
|
|
932
|
+
})()
|
|
713
933
|
const authorizationTuple = [
|
|
714
934
|
bigintToHex(chainId),
|
|
715
935
|
type,
|
|
716
936
|
address,
|
|
717
|
-
// expiry is required in the tuple when limits are present
|
|
718
|
-
|
|
937
|
+
// expiry is required in the tuple when limits or scopes are present
|
|
938
|
+
// expiry=0 is treated the same as undefined (never expires)
|
|
939
|
+
(expiry !== null && expiry !== undefined && expiry !== 0) ||
|
|
940
|
+
limitsValue ||
|
|
941
|
+
callsValue
|
|
719
942
|
? numberToHex(expiry ?? 0)
|
|
720
943
|
: undefined,
|
|
721
|
-
|
|
722
|
-
|
|
944
|
+
// limits is required in the tuple when scopes are present
|
|
945
|
+
limitsValue || callsValue ? (limitsValue ?? []) : undefined,
|
|
946
|
+
callsValue,
|
|
947
|
+
].filter((x) => typeof x !== 'undefined')
|
|
723
948
|
return [authorizationTuple, ...(signature ? [signature] : [])] as never
|
|
724
949
|
}
|
|
725
950
|
|
|
@@ -745,3 +970,18 @@ function hexToBigint(hex: Hex.Hex): bigint {
|
|
|
745
970
|
function hexToNumber(hex: Hex.Hex): number {
|
|
746
971
|
return hex === '0x' ? 0 : Hex.toNumber(hex)
|
|
747
972
|
}
|
|
973
|
+
|
|
974
|
+
function normalizeSelector(selector: Hex.Hex | number[]): Hex.Hex {
|
|
975
|
+
if (typeof selector === 'string') return selector
|
|
976
|
+
if (Array.isArray(selector))
|
|
977
|
+
return Hex.fromBytes(new Uint8Array(selector)) as Hex.Hex
|
|
978
|
+
return selector
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function resolveSelector(
|
|
982
|
+
selector: Hex.Hex | string | undefined,
|
|
983
|
+
): Hex.Hex | undefined {
|
|
984
|
+
if (!selector) return undefined
|
|
985
|
+
if (selector.startsWith('0x')) return selector as Hex.Hex
|
|
986
|
+
return AbiItem.getSelector(selector)
|
|
987
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import * as Period from './Period.js'
|
|
3
|
+
|
|
4
|
+
describe('seconds', () => {
|
|
5
|
+
test('default', () => {
|
|
6
|
+
expect(Period.seconds(30)).toBe(30)
|
|
7
|
+
})
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
describe('minutes', () => {
|
|
11
|
+
test('default', () => {
|
|
12
|
+
expect(Period.minutes(5)).toBe(300)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('hours', () => {
|
|
17
|
+
test('default', () => {
|
|
18
|
+
expect(Period.hours(2)).toBe(7200)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('days', () => {
|
|
23
|
+
test('default', () => {
|
|
24
|
+
expect(Period.days(1)).toBe(86400)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('weeks', () => {
|
|
29
|
+
test('default', () => {
|
|
30
|
+
expect(Period.weeks(1)).toBe(604800)
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('months', () => {
|
|
35
|
+
test('default', () => {
|
|
36
|
+
expect(Period.months(1)).toBe(2592000)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('years', () => {
|
|
41
|
+
test('default', () => {
|
|
42
|
+
expect(Period.years(1)).toBe(31536000)
|
|
43
|
+
})
|
|
44
|
+
})
|
package/tempo/Period.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the number of seconds in `n` days.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts twoslash
|
|
6
|
+
* import { Period } from 'ox/tempo'
|
|
7
|
+
*
|
|
8
|
+
* const seconds = Period.days(1) // 86400
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function days(n: number) {
|
|
12
|
+
return n * 24 * 60 * 60
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Returns the number of seconds in `n` hours.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts twoslash
|
|
20
|
+
* import { Period } from 'ox/tempo'
|
|
21
|
+
*
|
|
22
|
+
* const seconds = Period.hours(2) // 7200
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function hours(n: number) {
|
|
26
|
+
return n * 60 * 60
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the number of seconds in `n` minutes.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts twoslash
|
|
34
|
+
* import { Period } from 'ox/tempo'
|
|
35
|
+
*
|
|
36
|
+
* const seconds = Period.minutes(5) // 300
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function minutes(n: number) {
|
|
40
|
+
return n * 60
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns the number of seconds in `n` months (30 days).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts twoslash
|
|
48
|
+
* import { Period } from 'ox/tempo'
|
|
49
|
+
*
|
|
50
|
+
* const seconds = Period.months(1) // 2592000
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function months(n: number) {
|
|
54
|
+
return n * 30 * 24 * 60 * 60
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the number of seconds in `n` seconds.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts twoslash
|
|
62
|
+
* import { Period } from 'ox/tempo'
|
|
63
|
+
*
|
|
64
|
+
* const seconds = Period.seconds(30) // 30
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function seconds(n: number) {
|
|
68
|
+
return n
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns the number of seconds in `n` weeks.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts twoslash
|
|
76
|
+
* import { Period } from 'ox/tempo'
|
|
77
|
+
*
|
|
78
|
+
* const seconds = Period.weeks(1) // 604800
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function weeks(n: number) {
|
|
82
|
+
return n * 7 * 24 * 60 * 60
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns the number of seconds in `n` years (365 days).
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts twoslash
|
|
90
|
+
* import { Period } from 'ox/tempo'
|
|
91
|
+
*
|
|
92
|
+
* const seconds = Period.years(1) // 31536000
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function years(n: number) {
|
|
96
|
+
return n * 365 * 24 * 60 * 60
|
|
97
|
+
}
|