ox 0.14.11 → 0.14.12

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