# Check Direct Minting Limits

> Read the live hourly and daily direct-minting rate limits and compute the maximum mint that fits both windows.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

Source: https://dev.flare.network/fassets/developer-guides/fassets-direct-minting-limits

## Overview[​](#overview "Direct link to Overview")

This guide reads the on-chain [direct minting](/fassets/direct-minting#rate-limits) rate limits, replays the tumbling-window state off-chain, and prints the maximum amount a single mint can request right now without being delayed.

Direct minting is throttled by the `MintingRateLimiter` library: an hourly and a daily window cap how much FXRP can be minted before the asset manager starts pushing new mintings into a future window via [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed). A pre-flight check lets a frontend or executor avoid surprising the user with a delay.

The complete runnable example is available in the [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fassets/direct-minting-limits.ts) repository.

## How the windows work[​](#how-the-windows-work "Direct link to How the windows work")

`MintingRateLimiter` library uses **clock-aligned tumbling windows**, not rolling windows. On initialization, the window start is snapped to a multiple of `windowSizeSeconds`:

```
windowStartTimestamp = block.timestamp - block.timestamp % windowSizeSeconds
```

With `windowSizeSeconds = 3600`, the hourly window aligns to UTC hour boundaries (00:00-01:00, 01:00-02:00, …). The daily window (`86400` seconds) aligns to 00:00 UTC.

On every write, the limiter advances the window:

```
windowsElapsed        = (now - windowStartTimestamp) / windowSizeSecondsmintedInCurrentWindow = subOrZero(mintedInCurrentWindow, windowsElapsed * maxPerWindow)windowStartTimestamp += windowsElapsed * windowSizeSeconds
```

Two consequences worth noting:

1.  **Unused capacity does not roll over.** `subOrZero` clamps at zero, so an idle hour does not give twice the cap the next hour — every fresh window starts at exactly `maxPerWindow`.
2.  **Over-cap mints are delayed, not rejected.** `executionAllowedAt` is set to `windowStartTimestamp + windowSize * mintedInCurrentWindow / maxPerWindow`, so overflow drains hour-by-hour through the `subOrZero` step.

Reading the limiter state on its own returns stale `(windowStart, minted)` values until the next write touches the contract, so this script replays the slide off-chain to show the values as they would be right now.

A mint can be delayed by either the hourly/daily window or the large-minting threshold — whichever pushes [`executionAllowedAt`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) further into the future wins.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

-   The [flare-viem-starter](https://github.com/flare-foundation/flare-viem-starter) cloned locally with dependencies installed.
-   A configured `publicClient` pointing at the [Flare network](/network/overview) you want to query (Coston2 by default in the starter).

## Direct Minting Limits Script[​](#direct-minting-limits-script "Direct link to Direct Minting Limits Script")

src/fassets/direct-minting-limits.ts

```
import { dropsToXrp } from "xrpl";import { getContractAddressByName } from "./utils/flare-contract-registry";import {  getAssetMintingGranularityUBA,  getDirectMintingDailyLimitUBA,  getDirectMintingDailyLimiterState,  getDirectMintingHourlyLimitUBA,  getDirectMintingHourlyLimiterState,  getDirectMintingLargeMintingDelaySeconds,  getDirectMintingLargeMintingThresholdUBA,  getDirectMintingsUnblockUntilTimestamp,} from "./utils/fassets";// 1. Window sizes are clock-aligned tumbling, not rolling. Hourly snaps to//    UTC hour boundaries; daily snaps to 00:00 UTC.const HOURLY_WINDOW_SECONDS = 3600n;const DAILY_WINDOW_SECONDS = 86400n;// 2. Format helpers.function formatUba(uba: bigint): string {  return `${uba.toString()} UBA (${dropsToXrp(uba.toString())} XRP)`;}function formatTimestamp(secondsSinceEpoch: bigint, now: bigint): string {  const iso = new Date(Number(secondsSinceEpoch) * 1000).toISOString();  const delta = Number(secondsSinceEpoch - now);  const relative = delta >= 0 ? `in ${delta}s` : `${-delta}s ago`;  return `${iso} (${relative})`;}function bigintMin(a: bigint, b: bigint): bigint {  return a < b ? a : b;}// 3. Replay the limiter slide off-chain. Reading state alone returns stale//    `(windowStart, minted)` values until the next write touches the limiter,//    so we re-anchor the window and drain `mintedInCurrentWindow` using the//    same window-advancement logic as MintingRateLimiter.sol.function computeWindowState({  now,  windowStartTimestamp,  mintedInCurrentWindowUBA,  limitUBA,  windowSizeSeconds,}: {  now: bigint;  windowStartTimestamp: bigint;  mintedInCurrentWindowUBA: bigint;  limitUBA: bigint;  windowSizeSeconds: bigint;}) {  let effectiveStart = windowStartTimestamp;  let usedUBA = mintedInCurrentWindowUBA;  if (    windowStartTimestamp > 0n &&    now >= windowStartTimestamp + windowSizeSeconds  ) {    const windowsElapsed = (now - windowStartTimestamp) / windowSizeSeconds;    effectiveStart = windowStartTimestamp + windowsElapsed * windowSizeSeconds;    const drained = windowsElapsed * limitUBA;    usedUBA = drained >= usedUBA ? 0n : usedUBA - drained;  }  const remainingUBA = limitUBA > usedUBA ? limitUBA - usedUBA : 0n;  const nextResetAt = effectiveStart + windowSizeSeconds;  return { effectiveStart, usedUBA, remainingUBA, nextResetAt };}// 4. Print one window.function printWindow(  label: string,  opts: {    limitUBA: bigint;    usedUBA: bigint;    remainingUBA: bigint;    effectiveStart: bigint;    nextResetAt: bigint;    now: bigint;  },) {  const { limitUBA, usedUBA, remainingUBA, effectiveStart, nextResetAt, now } =    opts;  const usedPct =    limitUBA === 0n ? 0 : Number((usedUBA * 10000n) / limitUBA) / 100;  const row = (key: string, value: string) =>    console.log(`${key.padEnd(17)} ${value}`);  console.log(`=== ${label} ===`);  row("Limit:", formatUba(limitUBA));  row("Used:", `${formatUba(usedUBA)} (${usedPct.toFixed(2)}%)`);  row("Remaining:", formatUba(remainingUBA));  row("Window started:", formatTimestamp(effectiveStart, now));  row("Window resets at:", formatTimestamp(nextResetAt, now));  console.log();}async function main() {  // 5. Resolve `AssetManagerFXRP` through the Flare Contract Registry.  const assetManagerAddress =    await getContractAddressByName("AssetManagerFXRP");  console.log("AssetManagerFXRP address:", assetManagerAddress, "\n");  // 6. Read AMG granularity, hourly and daily caps, raw limiter state, the  //    unblock flag, and the large-minting threshold and delay in parallel.  const [    amgGranularityUBA,    hourlyLimitUBA,    dailyLimitUBA,    hourlyState,    dailyState,    unblockUntilTimestamp,    largeThresholdUBA,    largeDelaySeconds,  ] = await Promise.all([    getAssetMintingGranularityUBA(assetManagerAddress),    getDirectMintingHourlyLimitUBA(assetManagerAddress),    getDirectMintingDailyLimitUBA(assetManagerAddress),    getDirectMintingHourlyLimiterState(assetManagerAddress),    getDirectMintingDailyLimiterState(assetManagerAddress),    getDirectMintingsUnblockUntilTimestamp(assetManagerAddress),    getDirectMintingLargeMintingThresholdUBA(assetManagerAddress),    getDirectMintingLargeMintingDelaySeconds(assetManagerAddress),  ]);  const now = BigInt(Math.floor(Date.now() / 1000));  const limiterDisabled = unblockUntilTimestamp > now;  // 7. Limiter state is returned as raw AMG (uint64); convert to UBA via  //    `assetMintingGranularityUBA` before passing into the window math.  const computeAndPrint = (    label: string,    limitUBA: bigint,    state: readonly [bigint, bigint],    sizeSeconds: bigint,  ) => {    const [windowStart, mintedAmg] = state;    const result = computeWindowState({      now,      windowStartTimestamp: windowStart,      mintedInCurrentWindowUBA: mintedAmg * amgGranularityUBA,      limitUBA,      windowSizeSeconds: sizeSeconds,    });    printWindow(label, { ...result, limitUBA, now });    return result;  };  const hourly = computeAndPrint(    "Hourly window",    hourlyLimitUBA,    hourlyState,    HOURLY_WINDOW_SECONDS,  );  const daily = computeAndPrint(    "Daily window",    dailyLimitUBA,    dailyState,    DAILY_WINDOW_SECONDS,  );  // 8. Honor the unblock flag. While `directMintingsUnblockUntilTimestamp`  //    is in the future, governance has temporarily disabled the limiter  //    and the full caps are available.  console.log("=== Other flags ===");  if (limiterDisabled) {    console.log(      "Limiter DISABLED until:",      formatTimestamp(unblockUntilTimestamp, now),      "— hourly/daily caps are not enforced right now.",    );  } else {    console.log(      "Limiter active (unblockUntilTimestamp =",      unblockUntilTimestamp.toString() + ")",    );  }  console.log("Large minting threshold:", formatUba(largeThresholdUBA));  console.log("Large minting delay:    ", `${largeDelaySeconds.toString()}s`);  console.log();  // 9. Pre-flight gate: how much can a single mint safely request right now  //    without being delayed? It must fit both the hourly and daily windows.  const hourlyHeadroomUBA = limiterDisabled    ? hourlyLimitUBA    : hourly.remainingUBA;  const dailyHeadroomUBA = limiterDisabled ? dailyLimitUBA : daily.remainingUBA;  const safeRemainingUBA = bigintMin(hourlyHeadroomUBA, dailyHeadroomUBA);  console.log(    "Maximum single mint that fits both windows:",    formatUba(safeRemainingUBA),  );}void main()  .then(() => process.exit(0))  .catch((error) => {    console.error(error);    process.exit(1);  });
```

## Code Breakdown[​](#code-breakdown "Direct link to Code Breakdown")

1.  **Window sizes.** `HOURLY_WINDOW_SECONDS` and `DAILY_WINDOW_SECONDS` are the `windowSizeSeconds` values the `MintingRateLimiter.sol` library uses — `3600` aligns to UTC hour boundaries, `86400` aligns to 00:00 UTC.
2.  **Format helpers.** `formatUba` prints raw UBA (units of basis assets) alongside the XRP equivalent via `dropsToXrp`, and `formatTimestamp` shows ISO time with a relative offset so it is clear whether a timestamp is in the future or the past.
3.  **Replay the limiter slide.** `computeWindowState` mirrors the window-advancement logic in `MintingRateLimiter.sol`: it advances `windowStart` past every tumble that has elapsed and drains `mintedInCurrentWindow` by `windowsElapsed * limit`. Without this off-chain replay, you would see stale numbers between writes.
4.  **Print one window's live state.** `printWindow` shows the live cap, used amount, and percentage, remaining headroom, the effective window start, and the next UTC tumble.
5.  **Resolve `AssetManagerFXRP`.** `getContractAddressByName("AssetManagerFXRP")` looks up the asset manager through the [Flare Contract Registry](/network/guides/flare-contracts-registry).
6.  **Read everything in parallel.** `Promise.all` fetches the [AMG granularity](/fassets/reference/IAssetManager#assetmintinggranularityuba), the [hourly](/fassets/reference/IAssetManager#getdirectmintinghourlylimituba) and [daily](/fassets/reference/IAssetManager#getdirectmintingdailylimituba) caps, the raw limiter state for [hourly](/fassets/reference/IAssetManager#getdirectmintinghourlylimiterstate) and [daily](/fassets/reference/IAssetManager#getdirectmintingdailylimiterstate) windows, the [`unblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp), and the [large-minting threshold](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) and [delay](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) in one round trip.
7.  **Convert AMG to UBA.** The limiter stores `mintedInCurrentWindow` in AMG (`uint64`) for cheap on-chain storage. Multiplying by [`assetMintingGranularityUBA`](/fassets/reference/IAssetManager#assetmintinggranularityuba) rebases it into UBA (units of basis assets) so it can be compared against the UBA-denominated cap.
8.  **Honor the unblock flag.** When [`getDirectMintingsUnblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp) returns a future timestamp, governance has temporarily turned off the limiter; treat full caps as available.
9.  **Pre-flight gate.** `bigintMin(hourlyHeadroom, dailyHeadroom)` is the largest amount that fits both windows. A request larger than this will not revert — it will mint with [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) and an `executionAllowedAt` in the future.

## Important Notes[​](#important-notes "Direct link to Important Notes")

-   **Large mintings have an independent fixed delay.** Mintings above [`directMintingLargeMintingThresholdUBA`](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) are delayed by [`directMintingLargeMintingDelaySeconds`](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) even if the hourly and daily windows have headroom.
-   **Reads are stale between writes.** Always advance the window off-chain when displaying live limiter state.
-   **Plan UI flows around [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed)** so users are not surprised by a deferred execution.

What's next

To continue your FAssets development journey, you can:

-   Mint with the 32-byte memo flow in [Direct Mint FXRP](/fassets/developer-guides/fassets-direct-minting).
-   Mint with a reserved tag in [Direct Mint FXRP with Tag](/fassets/developer-guides/fassets-direct-minting-tag).
-   Read the protocol details in [Direct Minting](/fassets/direct-minting).
