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.
Files changed (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/_cjs/erc8021/Attribution.js +7 -1
  3. package/_cjs/erc8021/Attribution.js.map +1 -1
  4. package/_cjs/tempo/KeyAuthorization.js +150 -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 +159 -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 +95 -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 +407 -3
  32. package/tempo/KeyAuthorization.ts +291 -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 +969 -1
  37. package/tempo/index.ts +30 -0
  38. 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?: readonly TokenLimit<bigintType, addressType>[] | undefined
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 of an {@link ox#KeyAuthorization.KeyAuthorization}. */
64
- export type Rpc = Omit<
65
- KeyAuthorization<false, Hex.Hex, Hex.Hex>,
66
- 'address' | 'signature' | 'type'
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
- 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
- ]
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<bigintType = bigint, addressType = Address.Address> = {
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 } = authorization
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: typeof expiry !== 'undefined' ? hexToNumber(expiry) : undefined,
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' ? { expiry: hexToNumber(expiry) } : {}),
429
- ...(typeof limits !== 'undefined'
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(([token, limit]) => ({
432
- token,
433
- limit: hexToBigint(limit),
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 } = authorization
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
- limits: limits?.map(({ token, limit }) => ({
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
- keyType: type,
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
- bigintToHex(limit.limit),
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
- typeof expiry === 'number' || limitsValue
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
- limitsValue,
722
- ].filter(Boolean)
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,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
+ }