Enforces opt-in, rewarded-first monetization over the service seam — a rewarded view grants exactly one benefit and only on a genuine reward outcome (a sealed Rewarded/Dismissed/NoFill), earn loops capped per day against an injected Clock, the earn control preloaded and hidden unless an ad is loaded so a fresh account never dead-ends on "no ads available" (a Guideline 2.1 risk), interstitials only at a natural break under a count-and-elapsed cap owned by the service so callers stay dumb, no banner on the primary work surface, one non-consumable unlock plus Restore re-checked each launch and committed through the single write path before the UI reacts, one derived isEntitled provider as the only gate, and the entitlement restored BEFORE the ad SDK initializes so a paying user never starts it, resolves consent, or sees a tracking prompt. Use when adding ads, wiring a rewarded earn loop, placing an interstitial, building a paywall, gating an entitlement, or setting up ad units.
The policy layer over the ads and billing seams: what a rewarded view grants, when an
interstitial may fire and how often, how an entitlement gates both, and in what order they
initialize at launch. It assumes the seam already exists — declaring the interface, overriding it per
flavor, and keeping SDK types out of feature code belong to service-boundary-and-native.
The model this encodes is free, opt-in-first: value is exchanged for attention the user chose to
give, and one non-consumable purchase removes the exchange. It is not the only viable model, but the
rules below are what keep any ad-funded app honest, reviewable, and out of the "feels like adware"
bucket.
Non-negotiable rules
Every ad and store call goes through an injected interface, never an SDK type in feature code.AdsService, BillingService; the concrete SDK is wired once at the composition root and faked
in tests. Mechanism is owned by service-boundary-and-native — do not re-derive it here.
A rewarded view is opt-in and grants exactly one concrete benefit. It fires only from an
explicit "watch to earn" tap, never automatically, and it hands back one unit — a credit, an
extra attempt, a partial reveal — never the whole outcome the user is working toward. WHY: an
auto-playing "rewarded" ad is an interstitial wearing a costume, and a grant that finishes the
task for the user destroys the thing they came for.
Grant only on a genuine reward outcome. Model the result as a sealed type
(Rewarded / Dismissed / NoFill) and switch exhaustively; Dismissed (closed early) and
NoFill grant nothing and leave state untouched. WHY: a bool return collapses "no ad existed"
into "user declined" and the two need different UI.
Cap the earn loop per day, in the policy layer, against an injected Clock. An uncapped
watch-to-earn loop lets any user bypass whatever the benefit was rationing, and the ceiling
belongs next to the rule it protects — not in the ad network console (see
references/ad-unit-setup.md for why a console-side frequency cap actively harms an opt-in
format). Key the cap to a calendar day derived from clock.now(), never DateTime.now().
Never ship an earn control that can dead-end. A brand-new ad account has near-zero fill, so an
always-visible "watch to earn" button answers "no ads available" every time — poor UX and a
Guideline 2.1 rejection risk for a control that looks broken. Preload, expose availability as
a stream, and show the control only while an ad is actually loaded (or a redemption is
in flight). Whatever the user needs must remain reachable without watching anything.
Interstitials fire only at a natural break, under a two-part cap the service owns. The only
eligible moment is a completion or transition boundary the user already expected — never
mid-task, never during onboarding, never in the first session. Both halves of the cap must pass:
at most one per N completions and at least M seconds since the last one. Put the counting and
the clock inside maybeShowInterstitial(), and return whether it showed, so no caller counts
anything. WHY: a cap enforced at three call sites is three caps that will disagree.
No banner on the primary work surface. A banner on the screen where the user is actually doing
the thing reflows layout, invites mis-taps, and earns the least of any format. A menu or home
surface is the only defensible placement, and it is optional.
One non-consumable unlock plus Restore, re-checked on every launch. ⚠️ On the app's
first release the unlock must be submitted in the same App Review submission as the
app version — Apple requires the first purchase of each type to ride a version, and
shipping the version alone gets the whole submission closed unreviewed under Guideline
2.1(b) (release-and-store-shipping). Sell it once; expose a
visible Restore; silently re-check entitlement at launch so a reinstall or a new device
re-grants it without a support ticket. Not a subscription for a one-time capability, and not a
consumable currency sold for cash where an opt-in rewarded path already exists.
One derived isEntitled provider is the only gate. Everything — every ad call site, every cap,
every paywall affordance — reads it. Nothing branches on the raw entitlement stream. WHY: a second
read of the same truth is a second gate that will drift out of agreement with the first.
Restore the entitlement BEFORE initializing the ad SDK. No-op-ing ad calls is not enough: if
AdsService.init() already ran, a paying user has had the SDK started, consent resolved, a
tracking prompt shown, and their device identified — everything they paid to avoid. Await the
restore first and initialize ads only for a confirmed non-entitled user. A store or network
failure resolves to not entitled and initializes — never withhold ads from every free user
because one call flaked — so cache the last known entitlement locally to keep an offline launch
correct for an owner. See references/entitlement-and-restore.md.
A purchase persists through the single write path before the UI reacts. One committed
transaction through the repository, then the UI updates because isEntitled re-emits — never an
optimistic dismiss. WHY: a crash mid-flow must never leave a "paid but not entitled" ghost.
Write-path mechanics are owned by persistence-drift.
Never paid-upfront and ad-supported. Pick one. Charging for the app and then monetizing the
buyer's attention is the pattern users punish in reviews, and it makes the paywall unarguable.
Ad unit identifiers are configuration, not secrets — but sample ids must never ship. They are
visible in any decompiled app, so compile-time constants beat --dart-define (a release build
cannot forget a constant). Gate the release on it: scripts/check-release-ad-ids.sh fails a
release that still carries the network's well-known sample ids, which serve "test ad" creatives
and read as broken to a reviewer.
The earn loop, end to end
// Policy layer: depends on the interfaces and the entitlement gate — never on an SDK.
sealed class RewardOutcome { const RewardOutcome(); }
final class Rewarded extends RewardOutcome { const Rewarded(); }
final class Dismissed extends RewardOutcome { const Dismissed(); } // closed early
final class NoFill extends RewardOutcome { const NoFill(); } // no ad existed
class CreditEconomy extends Notifier<CreditState> {
static const int freeCreditsPerDay = 1; // the allowance the design assumes
static const int maxEarnedPerDay = 3; // the integrity ceiling (rule 4)
@override
CreditState build() => const CreditState();
/// Opt-in earn path. Grants ONLY on `Rewarded`, and only under the daily cap.
Future<Result<Credit, MonetizationFailure>> watchToEarn() async {
if (ref.read(isEntitledProvider).valueOrNull ?? false) return _grant(); // no ad at all
if (state.earnedToday(ref.read(clockProvider)) >= maxEarnedPerDay) {
return const Err(EarnCapReached());
}
return switch (await ref.read(adsServiceProvider).showRewarded()) {
Rewarded() => _grant(),
Dismissed() => const Err(RewardNotEarned()), // distinct: the user closed it
NoFill() => const Err(NoAdAvailable()), // distinct: nothing to show
};
}
}
The control that calls it is bound to availability, so it is never a dead end (rule 5):
// Shown only while an ad is genuinely loaded; the free path stays reachable regardless.
final rewardedReadyProvider =
StreamProvider<bool>((ref) => ref.watch(adsServiceProvider).rewardedAvailability);
if (ref.watch(rewardedReadyProvider).valueOrNull ?? false)
const WatchToEarnButton(),
And the interstitial call site counts nothing (rule 6):
Future<void> onTaskCompleted(WidgetRef ref) async {
if (ref.read(isEntitledProvider).valueOrNull ?? false) return; // rule 9
await ref.read(adsServiceProvider).maybeShowInterstitial(); // owns both cap halves
}
Launch order
// Entitlement first, ads only if the user is not entitled (rule 10).
// Startup-sequence mechanics are owned by `app-startup-and-bootstrap`.
final entitlement = await ref.read(billingServiceProvider).restore(); // failure ⇒ not entitled
if (!entitlement.isEntitled) {
await ref.read(adsServiceProvider).init(); // consent / tracking prompt happens in here
await ref.read(adsServiceProvider).preloadRewarded();
}
Cover it with a test asserting the fake ads service records initCount == 0 for an entitled user —
that single assertion is what keeps rule 10 true through future refactors.
Anti-patterns
Auto-playing a "rewarded" ad, or granting on Dismissed/NoFill — it is opt-in, and the grant
is for a completed view only.
A bool rewarded result — "no fill" and "user closed it" need different UI; use the sealed type.
An uncapped earn loop, or a cap enforced in the ad console instead of the app — the console cap
produces no-fill, and no-fill hides the control (rule 5), so the user loses the path with no
visible reason.
An always-visible earn button on a new account — permanent "no ads available"; Guideline 2.1.
A grant that completes the user's task — reveal a step, never the destination.
Interstitials mid-task, on first launch, during onboarding, or a caller that counts completions
or reads a clock — the break is the only trigger and the cap lives in the service.
A banner on the working surface — the one placement that is never worth it.
Widgets reading the raw entitlement stream — everything reads the one derived gate.
No-op-ing ads for an entitled user without moving the restore before init() — they still got
the SDK, the consent flow, and the tracking prompt.
Blocking ads for everyone when a restore call fails — failure means not entitled; cache the
last known state so an offline owner stays correct.
Dismissing the paywall before the entitlement is durably written — persist, then let the gate re-emit.
Skipping the launch restore — reinstalls and device swaps become support tickets.
Paid-upfront plus ads, or a subscription for a one-time unlock.
Shipping the network's sample ad ids — "test ad" creatives earn nothing and look broken on review.
Definition of done
Ads and billing are reached only through injected interfaces; no SDK type appears in feature code.
Rewarded is opt-in, grants exactly one benefit, and grants only on Rewarded; the outcome
type is sealed and switched exhaustively.
The earn loop is capped per calendar day against the injected Clock; the cap lives in the app.
The earn control is preloaded and hidden unless an ad is loaded; the task stays completable
without watching anything.
Interstitials fire only at a break; maybeShowInterstitial() owns the count and elapsed
caps and reports whether it showed; no caller counts.
No banner on any primary work surface.
One non-consumable + a visible Restore; entitlement silently re-checked at every launch.
On a first release, the product ships in the same submission as the app version, with
its App Review screenshot, tax category, availability, and one localization per app locale.
isEntitled is the single gate; no feature branches on the raw entitlement stream.
Restore runs beforeAdsService.init(); a test asserts the fake's initCount == 0 for an
entitled user; a failed restore falls back to not entitled with a local cache for offline.
The purchase commits through the single write path before any UI reacts.
Release ad ids verified by scripts/check-release-ad-ids.sh; no sample ids in a release build.
Tests drive: watch → granted, cap reached → refused, no-fill → control hidden, purchase →
ads gone and caps lifted, against fakes.
Related skills
See service-boundary-and-native for declaring AdsService/BillingService, the per-flavor live
implementation, and the fakes these tests use.
See app-startup-and-bootstrap for where the restore-then-init sequence belongs in main().
See state-management-riverpod for the derived-gate provider and persistence-drift for the
single write path the purchase commits through.
See error-handling-typed-results for the Result/Failure arms returned above.
See release-and-store-shipping for the privacy declaration the ad SDK drives and the reviewer note that
explains empty ad inventory during review.
1---2name: ads-and-iap-monetization3description: Enforces opt-in, rewarded-first monetization over the service seam — a rewarded view grants exactly one benefit and only on a genuine reward outcome (a sealed Rewarded/Dismissed/NoFill), earn loops capped per day against an injected Clock, the earn control preloaded and hidden unless an ad is loaded so a fresh account never dead-ends on "no ads available" (a Guideline 2.1 risk), interstitials only at a natural break under a count-and-elapsed cap owned by the service so callers stay dumb, no banner on the primary work surface, one non-consumable unlock plus Restore re-checked each launch and committed through the single write path before the UI reacts, one derived isEntitled provider as the only gate, and the entitlement restored BEFORE the ad SDK initializes so a paying user never starts it, resolves consent, or sees a tracking prompt. Use when adding ads, wiring a rewarded earn loop, placing an interstitial, building a paywall, gating an entitlement, or setting up ad units.4---56# Ads and IAP monetization78The policy layer over the ads and billing seams: *what* a rewarded view grants, *when* an9interstitial may fire and how often, *how* an entitlement gates both, and *in what order* they10initialize at launch. It assumes the seam already exists — declaring the interface, overriding it per11flavor, and keeping SDK types out of feature code belong to `service-boundary-and-native`.1213The model this encodes is **free, opt-in-first**: value is exchanged for attention the user chose to14give, and one non-consumable purchase removes the exchange. It is not the only viable model, but the15rules below are what keep any ad-funded app honest, reviewable, and out of the "feels like adware"16bucket.1718## Non-negotiable rules19201. **Every ad and store call goes through an injected interface, never an SDK type in feature code.**21 `AdsService`, `BillingService`; the concrete SDK is wired once at the composition root and faked22 in tests. Mechanism is owned by `service-boundary-and-native` — do not re-derive it here.23242. **A rewarded view is opt-in and grants exactly one concrete benefit.** It fires only from an25 explicit "watch to earn" tap, never automatically, and it hands back **one** unit — a credit, an26 extra attempt, a partial reveal — never the whole outcome the user is working toward. WHY: an27 auto-playing "rewarded" ad is an interstitial wearing a costume, and a grant that finishes the28 task for the user destroys the thing they came for.29303. **Grant only on a genuine reward outcome.** Model the result as a sealed type31 (`Rewarded` / `Dismissed` / `NoFill`) and switch exhaustively; `Dismissed` (closed early) and32 `NoFill` grant nothing and leave state untouched. WHY: a `bool` return collapses "no ad existed"33 into "user declined" and the two need different UI.34354. **Cap the earn loop per day, in the policy layer, against an injected `Clock`.** An uncapped36 watch-to-earn loop lets any user bypass whatever the benefit was rationing, and the ceiling37 belongs next to the rule it protects — not in the ad network console (see38 `references/ad-unit-setup.md` for why a console-side frequency cap actively harms an opt-in39 format). Key the cap to a calendar day derived from `clock.now()`, never `DateTime.now()`.40415. **Never ship an earn control that can dead-end.** A brand-new ad account has near-zero fill, so an42 always-visible "watch to earn" button answers *"no ads available"* every time — poor UX and a43 **Guideline 2.1** rejection risk for a control that looks broken. Preload, expose availability as44 a stream, and show the control **only while an ad is actually loaded** (or a redemption is45 in flight). Whatever the user needs must remain reachable without watching anything.46476. **Interstitials fire only at a natural break, under a two-part cap the service owns.** The only48 eligible moment is a completion or transition boundary the user already expected — never49 mid-task, never during onboarding, never in the first session. Both halves of the cap must pass:50 at most one per N completions **and** at least M seconds since the last one. Put the counting and51 the clock inside `maybeShowInterstitial()`, and return whether it showed, so no caller counts52 anything. WHY: a cap enforced at three call sites is three caps that will disagree.53547. **No banner on the primary work surface.** A banner on the screen where the user is actually doing55 the thing reflows layout, invites mis-taps, and earns the least of any format. A menu or home56 surface is the only defensible placement, and it is optional.57588. **One non-consumable unlock plus Restore, re-checked on every launch.** ⚠️ On the app's59 **first** release the unlock must be submitted **in the same App Review submission as the60 app version** — Apple requires the first purchase of each type to ride a version, and61 shipping the version alone gets the whole submission closed unreviewed under Guideline62 2.1(b) (`release-and-store-shipping`). Sell it once; expose a63 visible **Restore**; silently re-check entitlement at launch so a reinstall or a new device64 re-grants it without a support ticket. Not a subscription for a one-time capability, and not a65 consumable currency sold for cash where an opt-in rewarded path already exists.66679. **One derived `isEntitled` provider is the only gate.** Everything — every ad call site, every cap,68 every paywall affordance — reads it. Nothing branches on the raw entitlement stream. WHY: a second69 read of the same truth is a second gate that will drift out of agreement with the first.707110. **Restore the entitlement BEFORE initializing the ad SDK.** No-op-ing ad calls is not enough: if72 `AdsService.init()` already ran, a paying user has had the SDK started, consent resolved, a73 tracking prompt shown, and their device identified — everything they paid to avoid. Await the74 restore first and initialize ads **only** for a confirmed non-entitled user. A store or network75 failure resolves to *not entitled* and initializes — never withhold ads from every free user76 because one call flaked — so cache the last known entitlement locally to keep an offline launch77 correct for an owner. See `references/entitlement-and-restore.md`.787911. **A purchase persists through the single write path before the UI reacts.** One committed80 transaction through the repository, then the UI updates because `isEntitled` re-emits — never an81 optimistic dismiss. WHY: a crash mid-flow must never leave a "paid but not entitled" ghost.82 Write-path mechanics are owned by `persistence-drift`.838412. **Never paid-upfront *and* ad-supported.** Pick one. Charging for the app and then monetizing the85 buyer's attention is the pattern users punish in reviews, and it makes the paywall unarguable.868713. **Ad unit identifiers are configuration, not secrets — but sample ids must never ship.** They are88 visible in any decompiled app, so compile-time constants beat `--dart-define` (a release build89 cannot forget a constant). Gate the release on it: `scripts/check-release-ad-ids.sh` fails a90 release that still carries the network's well-known sample ids, which serve "test ad" creatives91 and read as broken to a reviewer.9293## The earn loop, end to end9495```dart96// Policy layer: depends on the interfaces and the entitlement gate — never on an SDK.97sealed class RewardOutcome { const RewardOutcome(); }98final class Rewarded extends RewardOutcome { const Rewarded(); }99final class Dismissed extends RewardOutcome { const Dismissed(); } // closed early100final class NoFill extends RewardOutcome { const NoFill(); } // no ad existed101102class CreditEconomy extends Notifier<CreditState> {103 static const int freeCreditsPerDay = 1; // the allowance the design assumes104 static const int maxEarnedPerDay = 3; // the integrity ceiling (rule 4)105106 @override107 CreditState build() => const CreditState();108109 /// Opt-in earn path. Grants ONLY on `Rewarded`, and only under the daily cap.110 Future<Result<Credit, MonetizationFailure>> watchToEarn() async {111 if (ref.read(isEntitledProvider).valueOrNull ?? false) return _grant(); // no ad at all112 if (state.earnedToday(ref.read(clockProvider)) >= maxEarnedPerDay) {113 return const Err(EarnCapReached());114 }115 return switch (await ref.read(adsServiceProvider).showRewarded()) {116 Rewarded() => _grant(),117 Dismissed() => const Err(RewardNotEarned()), // distinct: the user closed it118 NoFill() => const Err(NoAdAvailable()), // distinct: nothing to show119 };120 }121}122```123124The control that calls it is bound to availability, so it is never a dead end (rule 5):125126```dart127// Shown only while an ad is genuinely loaded; the free path stays reachable regardless.128final rewardedReadyProvider =129 StreamProvider<bool>((ref) => ref.watch(adsServiceProvider).rewardedAvailability);130131if (ref.watch(rewardedReadyProvider).valueOrNull ?? false)132 const WatchToEarnButton(),133```134135And the interstitial call site counts nothing (rule 6):136137```dart138Future<void> onTaskCompleted(WidgetRef ref) async {139 if (ref.read(isEntitledProvider).valueOrNull ?? false) return; // rule 9140 await ref.read(adsServiceProvider).maybeShowInterstitial(); // owns both cap halves141}142```143144## Launch order145146```dart147// Entitlement first, ads only if the user is not entitled (rule 10).148// Startup-sequence mechanics are owned by `app-startup-and-bootstrap`.149final entitlement = await ref.read(billingServiceProvider).restore(); // failure ⇒ not entitled150if (!entitlement.isEntitled) {151 await ref.read(adsServiceProvider).init(); // consent / tracking prompt happens in here152 await ref.read(adsServiceProvider).preloadRewarded();153}154```155156Cover it with a test asserting the fake ads service records `initCount == 0` for an entitled user —157that single assertion is what keeps rule 10 true through future refactors.158159## Anti-patterns160161- **Auto-playing a "rewarded" ad**, or granting on `Dismissed`/`NoFill` — it is opt-in, and the grant162 is for a completed view only.163- **A `bool` rewarded result** — "no fill" and "user closed it" need different UI; use the sealed type.164- **An uncapped earn loop**, or a cap enforced in the ad console instead of the app — the console cap165 produces no-fill, and no-fill *hides the control* (rule 5), so the user loses the path with no166 visible reason.167- **An always-visible earn button on a new account** — permanent "no ads available"; Guideline 2.1.168- **A grant that completes the user's task** — reveal a step, never the destination.169- **Interstitials mid-task, on first launch, during onboarding**, or a caller that counts completions170 or reads a clock — the break is the only trigger and the cap lives in the service.171- **A banner on the working surface** — the one placement that is never worth it.172- **Widgets reading the raw entitlement stream** — everything reads the one derived gate.173- **No-op-ing ads for an entitled user without moving the restore before `init()`** — they still got174 the SDK, the consent flow, and the tracking prompt.175- **Blocking ads for everyone when a restore call fails** — failure means *not entitled*; cache the176 last known state so an offline owner stays correct.177- **Dismissing the paywall before the entitlement is durably written** — persist, then let the gate re-emit.178- **Skipping the launch restore** — reinstalls and device swaps become support tickets.179- **Paid-upfront plus ads, or a subscription for a one-time unlock.**180- **Shipping the network's sample ad ids** — "test ad" creatives earn nothing and look broken on review.181182## Definition of done183184- [ ] Ads and billing are reached only through injected interfaces; no SDK type appears in feature code.185- [ ] Rewarded is opt-in, grants exactly one benefit, and grants **only** on `Rewarded`; the outcome186 type is sealed and switched exhaustively.187- [ ] The earn loop is capped per calendar day against the injected `Clock`; the cap lives in the app.188- [ ] The earn control is preloaded and hidden unless an ad is loaded; the task stays completable189 without watching anything.190- [ ] Interstitials fire only at a break; `maybeShowInterstitial()` owns the count **and** elapsed191 caps and reports whether it showed; no caller counts.192- [ ] No banner on any primary work surface.193- [ ] One non-consumable + a visible Restore; entitlement silently re-checked at every launch.194- [ ] On a first release, the product ships in the **same submission** as the app version, with195 its App Review screenshot, tax category, availability, and one localization per app locale.196- [ ] `isEntitled` is the single gate; no feature branches on the raw entitlement stream.197- [ ] Restore runs **before** `AdsService.init()`; a test asserts the fake's `initCount == 0` for an198 entitled user; a failed restore falls back to *not entitled* with a local cache for offline.199- [ ] The purchase commits through the single write path before any UI reacts.200- [ ] Release ad ids verified by `scripts/check-release-ad-ids.sh`; no sample ids in a release build.201- [ ] Tests drive: watch → granted, cap reached → refused, no-fill → control hidden, purchase →202 ads gone and caps lifted, against fakes.203204## Related skills205206- See `service-boundary-and-native` for declaring `AdsService`/`BillingService`, the per-flavor live207 implementation, and the fakes these tests use.208- See `app-startup-and-bootstrap` for where the restore-then-init sequence belongs in `main()`.209- See `state-management-riverpod` for the derived-gate provider and `persistence-drift` for the210 single write path the purchase commits through.211- See `error-handling-typed-results` for the `Result`/`Failure` arms returned above.212- See `release-and-store-shipping` for the privacy declaration the ad SDK drives and the reviewer note that213 explains empty ad inventory during review.214215## References216217- App Store Review Guidelines (2.1 App Completeness, 3.1 Payments): https://developer.apple.com/app-store/review/guidelines/218- `in_app_purchase` (StoreKit / Play Billing): https://pub.dev/packages/in_app_purchase219- Flutter — in-app purchases cookbook: https://docs.flutter.dev/cookbook/plugins/in-app-purchases220- `google_mobile_ads`: https://pub.dev/packages/google_mobile_ads221- AdMob rewarded ads (Flutter): https://developers.google.com/admob/flutter/rewarded222- User Messaging Platform / consent: https://developers.google.com/admob/flutter/privacy223- App Tracking Transparency: https://developer.apple.com/documentation/apptrackingtransparency
Run npx skillmds@latest add zakariaf/ads-and-iap-monetization in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Enforces opt-in, rewarded-first monetization over the service seam — a rewarded view grants exactly one benefit and only on a genuine reward outcome (a sealed Rewarded/Dismissed/NoFill), earn loops capped per day against an injected Clock, the earn control preloaded and hidden unless an ad is loaded so a fresh account never dead-ends on "no ads available" (a Guideline 2.1 risk), interstitials only at a natural break under a count-and-elapsed cap owned by the service so callers stay dumb, no banner on the primary work surface, one non-consumable unlock plus Restore re-checked each launch and committed through the single write path before the UI reacts, one derived isEntitled provider as the only gate, and the entitlement restored BEFORE the ad SDK initializes so a paying user never starts it, resolves consent, or sees a tracking prompt. Use when adding ads, wiring a rewarded earn loop, placing an interstitial, building a paywall, gating an entitlement, or setting up ad units. It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
zakariaf (@zakariaf) published this skill. Their other Agent Skills are listed on their SkillMD profile.