# Check Minting Limits

> Read live minting rate limits and pre-flight a mint against hourly, daily, and large-mint delays.

> 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-mint-limits

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

This guide reads the on-chain [minting](/fassets/minting#rate-limits) rate limits, replays the tumbling-window state off-chain, and pre-flights a proposed mint against **three independent delay mechanisms**:

1.  **Hourly window** — [`directMintingHourlyLimitUBA`](/fassets/reference/IAssetManager#getdirectmintinghourlylimituba) via the `MintingRateLimiter` library
2.  **Daily window** — [`directMintingDailyLimitUBA`](/fassets/reference/IAssetManager#getdirectmintingdailylimituba) via the same library
3.  **Large-mint rule** — amounts strictly above [`directMintingLargeMintingThresholdUBA`](/fassets/reference/IAssetManager#getdirectmintinglargemintingthresholduba) incur a fixed [`directMintingLargeMintingDelaySeconds`](/fassets/reference/IAssetManager#getdirectmintinglargemintingdelayseconds) hold

All three throttle rather than reject: over-limit mints emit [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed) with an `executionAllowedAt` timestamp. The binding delay is whichever rule pushes `executionAllowedAt` furthest into the future.

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` 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.

## Large minting delay[​](#large-minting-delay "Direct link to Large minting delay")

Large-mint delay is **not** part of `MintingRateLimiter`. It applies when the proposed mint amount is **strictly greater than** `directMintingLargeMintingThresholdUBA`:

```
if amount > directMintingLargeMintingThresholdUBA:    executionAllowedAt_large = now + directMintingLargeMintingDelaySeconds
```

This rule runs even when the hourly and daily windows have headroom. [`unblockDirectMintingsUntil`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp) bypasses the hourly and daily limiter only — it does **not** skip large-mint delay.

On Flare mainnet, the threshold and delay are currently **4M XRP** and **2 hours** respectively (see [Operational Parameters](/fassets/operational-parameters)).

## 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).

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

src/fassets/direct-minting-limits.ts

```
import { dropsToXrp, xrpToDrops } 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;// Example mint to pre-flight (net FXRP, before fees). Override with MINT_PREFLIGHT_XRP.const DEFAULT_PREFLIGHT_MINT_XRP = 10;// 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;}function bigintMax(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. Window delay mirrors MintingRateLimiter: overflow is scheduled into the//    current tumbling window proportionally to accumulated minted volume.function computeWindowExecutionAllowedAt({  now,  effectiveStart,  usedUBA,  proposedAmountUBA,  limitUBA,  windowSizeSeconds,  limiterDisabled,}: {  now: bigint;  effectiveStart: bigint;  usedUBA: bigint;  proposedAmountUBA: bigint;  limitUBA: bigint;  windowSizeSeconds: bigint;  limiterDisabled: boolean;}): bigint {  if (limiterDisabled || limitUBA === 0n || proposedAmountUBA === 0n) {    return now;  }  const mintedAfter = usedUBA + proposedAmountUBA;  if (mintedAfter <= limitUBA) {    return now;  }  return effectiveStart + (windowSizeSeconds * mintedAfter) / limitUBA;}// 5. Large-mint delay is independent of the hourly/daily windows. Amounts//    strictly above the threshold are held for a fixed duration from `now`.function computeLargeMintExecutionAllowedAt({  now,  proposedAmountUBA,  largeThresholdUBA,  largeDelaySeconds,}: {  now: bigint;  proposedAmountUBA: bigint;  largeThresholdUBA: bigint;  largeDelaySeconds: bigint;}): bigint {  if (proposedAmountUBA > largeThresholdUBA) {    return now + largeDelaySeconds;  }  return now;}function computeDirectMintingExecutionAllowedAt({  now,  proposedAmountUBA,  hourly,  daily,  largeThresholdUBA,  largeDelaySeconds,  limiterDisabled,}: {  now: bigint;  proposedAmountUBA: bigint;  hourly: {    effectiveStart: bigint;    usedUBA: bigint;    limitUBA: bigint;    windowSizeSeconds: bigint;  };  daily: {    effectiveStart: bigint;    usedUBA: bigint;    limitUBA: bigint;    windowSizeSeconds: bigint;  };  largeThresholdUBA: bigint;  largeDelaySeconds: bigint;  limiterDisabled: boolean;}) {  const hourlyAt = computeWindowExecutionAllowedAt({    now,    effectiveStart: hourly.effectiveStart,    usedUBA: hourly.usedUBA,    proposedAmountUBA,    limitUBA: hourly.limitUBA,    windowSizeSeconds: hourly.windowSizeSeconds,    limiterDisabled,  });  const dailyAt = computeWindowExecutionAllowedAt({    now,    effectiveStart: daily.effectiveStart,    usedUBA: daily.usedUBA,    proposedAmountUBA,    limitUBA: daily.limitUBA,    windowSizeSeconds: daily.windowSizeSeconds,    limiterDisabled,  });  const largeAt = computeLargeMintExecutionAllowedAt({    now,    proposedAmountUBA,    largeThresholdUBA,    largeDelaySeconds,  });  const executionAllowedAt = bigintMax(hourlyAt, bigintMax(dailyAt, largeAt));  const delayed = executionAllowedAt > now;  const delayReasons: string[] = [];  if (hourlyAt === executionAllowedAt && hourlyAt > now) {    delayReasons.push("hourly window");  }  if (dailyAt === executionAllowedAt && dailyAt > now) {    delayReasons.push("daily window");  }  if (largeAt === executionAllowedAt && largeAt > now) {    delayReasons.push("large-mint threshold");  }  return {    executionAllowedAt,    delayed,    delayReasons,    hourlyAt,    dailyAt,    largeAt,  };}// 6. 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() {  // 7. Resolve `AssetManagerFXRP` through the Flare Contract Registry.  const assetManagerAddress =    await getContractAddressByName("AssetManagerFXRP");  console.log("AssetManagerFXRP address:", assetManagerAddress, "\n");  // 8. 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;  // 9. 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,  );  // 10. Honor the unblock flag. While `directMintingsUnblockUntilTimestamp`  //     is in the future, governance has temporarily disabled the hourly/daily  //     limiter. Large-mint delay still applies.  console.log("=== Other flags ===");  if (limiterDisabled) {    console.log(      "Hourly/daily limiter DISABLED until:",      formatTimestamp(unblockUntilTimestamp, now),      "— window caps are not enforced right now.",    );    console.log(      "Large-mint delay is still enforced above",      formatUba(largeThresholdUBA) + ".",    );  } else {    console.log(      "Hourly/daily limiter active (unblockUntilTimestamp =",      unblockUntilTimestamp.toString() + ")",    );  }  console.log("Large minting threshold:", formatUba(largeThresholdUBA));  console.log("Large minting delay:    ", `${largeDelaySeconds.toString()}s`);  console.log();  // 11. Pre-flight gate: largest single mint with no delay from any mechanism.  const hourlyHeadroomUBA = limiterDisabled    ? hourlyLimitUBA    : hourly.remainingUBA;  const dailyHeadroomUBA = limiterDisabled ? dailyLimitUBA : daily.remainingUBA;  const safeRemainingUBA = bigintMin(    bigintMin(hourlyHeadroomUBA, dailyHeadroomUBA),    largeThresholdUBA,  );  console.log(    "Maximum single mint with no delay (hourly, daily, and large-mint):",    formatUba(safeRemainingUBA),  );  console.log();  // 12. Pre-flight a concrete mint amount, including large-mint delay.  const preflightMintXrp = Number(    process.env.MINT_PREFLIGHT_XRP ?? DEFAULT_PREFLIGHT_MINT_XRP,  );  const proposedAmountUBA = BigInt(xrpToDrops(preflightMintXrp.toString()));  const preflight = computeDirectMintingExecutionAllowedAt({    now,    proposedAmountUBA,    hourly: {      effectiveStart: hourly.effectiveStart,      usedUBA: hourly.usedUBA,      limitUBA: hourlyLimitUBA,      windowSizeSeconds: HOURLY_WINDOW_SECONDS,    },    daily: {      effectiveStart: daily.effectiveStart,      usedUBA: daily.usedUBA,      limitUBA: dailyLimitUBA,      windowSizeSeconds: DAILY_WINDOW_SECONDS,    },    largeThresholdUBA,    largeDelaySeconds,    limiterDisabled,  });  console.log(`=== Pre-flight: ${preflightMintXrp} XRP mint ===`);  if (!preflight.delayed) {    console.log("Result: executes immediately (no rate-limit delay).");  } else {    console.log(      "Result: would emit DirectMintingDelayed with executionAllowedAt",      formatTimestamp(preflight.executionAllowedAt, now),    );    console.log(      "Binding delay:",      preflight.delayReasons.length > 0        ? preflight.delayReasons.join(", ")        : "unknown",    );    console.log(      "Hourly window would allow at:",      formatTimestamp(preflight.hourlyAt, now),    );    console.log(      "Daily window would allow at:",      formatTimestamp(preflight.dailyAt, now),    );    console.log(      "Large-mint rule would allow at:",      formatTimestamp(preflight.largeAt, now),    );    console.log(      "The executor must wait, then call executeDirectMinting again with the same FDC proof.",    );  }  console.log();}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.  **Window delay math.** `computeWindowExecutionAllowedAt` applies the proportional overflow formula from `MintingRateLimiter`: when `used + proposed` exceeds the cap, `executionAllowedAt = effectiveStart + windowSize * (used + proposed) / limit`.
5.  **Large-mint delay math.** `computeLargeMintExecutionAllowedAt` returns `now + directMintingLargeMintingDelaySeconds` when `proposed > threshold`, otherwise `now`.
6.  **Combine all three.** `computeDirectMintingExecutionAllowedAt` takes the maximum of the hourly, daily, and large-mint timestamps and reports which rule is binding.
7.  **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.
8.  **Resolve `AssetManagerFXRP`.** `getContractAddressByName("AssetManagerFXRP")` looks up the asset manager through the [Flare Contract Registry](/network/guides/flare-contracts-registry).
9.  **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.
10.  **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.
11.  **Honor the unblock flag.** When [`getDirectMintingsUnblockUntilTimestamp`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp) returns a future timestamp, governance has temporarily disabled the hourly/daily limiter. Large-mint delay still applies.
12.  **Safe maximum without delay.** `bigintMin(hourlyHeadroom, dailyHeadroom, largeThresholdUBA)` is the largest amount that avoids delay from any mechanism. Minting exactly at the threshold is allowed; strictly above it triggers the large-mint hold.
13.  **Pre-flight a concrete amount.** Set `MINT_PREFLIGHT_XRP` (defaults to `10`) to see whether that mint would execute immediately or emit [`DirectMintingDelayed`](/fassets/reference/IAssetManagerEvents#directmintingdelayed). A delayed mint does not revert — the executor retries with the same FDC proof after `executionAllowedAt`.

## 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.
-   **Governance unblock is window-only.** [`unblockDirectMintingsUntil`](/fassets/reference/IAssetManager#getdirectmintingsunblockuntiltimestamp) drains hourly/daily backlogs; it does not bypass the large-mint rule.
-   **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.
-   **Delay and cap failures** — see [Delays](/fassets/troubleshooting/minting-troubleshooting#delays) in the Minting Troubleshooting guide for retry steps when rate limits bind.

What's next

To continue your FAssets development journey, you can:

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