diff --git a/README.md b/README.md
index f50cd02..ea61cdf 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,44 @@
# CloudX Trusted Arbiter Demo - Flutter
-A working Trusted Arbiter integration for interstitials. CloudX and AdMob load
-in parallel, both fills become bids, and `CloudX.arbiter` decides which one is
-shown. Companion to [docs.cloudx.io](https://docs.cloudx.io/en/flutter).
+A complete, runnable Flutter app showing how to put CloudX in competition with
+another ad network on price. CloudX and AdMob load an interstitial in parallel,
+both fills become bids, and `CloudX.arbiter` decides which one gets shown.
-Rewarded ads follow the same flow with one extra callback. Banners are a
-different shape entirely: `CloudX.createBanner` places a view at a fixed screen
-position and refreshes it on its own, with no arbiter round. Neither is
-repeated here; interstitial is the whole demo.
+Clone it and run it. It works out of the box against CloudX's public sample app,
+so you can watch a real auction before you change a single id.
+
+
+
+
+Interstitials only. Rewarded ads follow the same flow with one extra callback,
+and banners are a different shape entirely (`CloudX.createBanner` places a view
+at a fixed position and refreshes it on its own, with no arbiter round), so
+neither is repeated here.
+
+Full documentation: [Flutter integration
+guide](https://docs.cloudx.io/en/flutter/integration) and [Trusted
+Arbiter](https://docs.cloudx.io/en/flutter/trusted-arbiter).
+
+## Run it
+
+You need Flutter 3.44 or newer (Dart 3.12), and Xcode if you want the iOS side.
+Nothing else: the ids checked in belong to CloudX's public sample app, so the
+demo runs as-is.
+
+```sh
+flutter pub get
+(cd ios && pod install) # iOS only
+flutter run
+```
+
+Tap **Load both**, wait for both sides to settle, then tap **Show winner**. On
+iOS you will be asked for tracking permission first; see the ATT note under
+[Point it at your own app](#point-it-at-your-own-app) for why that has to come
+before anything else.
+
+If you want the iOS build on a device or an archive, set your own Signing Team
+in Xcode. The project deliberately ships with no `DEVELOPMENT_TEAM`, so signing
+stays on automatic and resolves to your team rather than ours.
## What Trusted Arbiter is
@@ -16,7 +47,7 @@ demand compete against CloudX on price instead of sitting in a waterfall above
or below it: you load both, hand both to CloudX as bids, and CloudX tells you
which one to show.
-Two rules are easy to miss, and both are in the code:
+Two rules are easy to miss, and both are visible in the code:
> **The arbiter runs before the show, not during it.** Both sides load, the
> arbiter picks a winner, and the winner is stored. The tap that shows an ad
@@ -34,11 +65,11 @@ AdMob bid therefore reaches the arbiter with no price at all. Expect CloudX to
lose those rounds. Point the demo at a real AdMob unit that pays to see prices
compete.
-## What is in here
+## What to copy into your app
-Copy [`lib/cloudx/`](lib/cloudx). Those six files are the whole integration,
-and none of them builds a widget. Everything outside that folder is this demo's
-own scaffolding.
+Copy [`lib/cloudx/`](lib/cloudx). Those six files are the whole integration, and
+none of them builds a widget, so they drop into an app with any UI. Everything
+outside that folder is this demo's own scaffolding.
| File | What it is |
|---|---|
@@ -48,10 +79,11 @@ own scaffolding.
| [`lib/cloudx/tracking_gate.dart`](lib/cloudx/tracking_gate.dart) | The iOS App Tracking Transparency gate. |
| [`lib/cloudx/cloudx_failure_text.dart`](lib/cloudx/cloudx_failure_text.dart) | One line out of a CloudX failure, carrying the SDK's own name for the error code. |
| [`lib/cloudx/demo_config.dart`](lib/cloudx/demo_config.dart) | App key and ad unit ids, per platform. The first file to edit. |
-| [`lib/ui/arbiter_screen.dart`](lib/ui/arbiter_screen.dart) | Demo-only UI. Ignore it when reading the integration. |
-| [`lib/main.dart`](lib/main.dart) | `runApp`, nothing else. |
-**Take fewer and it will not build.** The controller reports through
+`lib/ui/` and `lib/main.dart` are the demo's screen and entry point. They are
+here so the app runs; they are not part of the integration.
+
+**Take fewer files and it will not build.** The controller reports through
`arbiter_events.dart`, both it and `sdk_startup.dart` format failures through
`cloudx_failure_text.dart`, and `sdk_startup.dart` calls `tracking_gate.dart`.
If your app already initializes CloudX and answers the ATT prompt, drop
@@ -66,14 +98,7 @@ Read them in this order:
3. [`arbiter_interstitial_controller.dart`](lib/cloudx/arbiter_interstitial_controller.dart)
- the cycle itself
-**No CloudX or AdMob call lives outside `lib/cloudx/`.** That is checkable:
-`grep -rn "CloudX\.\|MobileAds\." lib/ui lib/main.dart` returns only two
-strings printed on screen. The integration files import
-`package:flutter/foundation.dart` for logging, and `tracking_gate.dart` also
-needs `package:flutter/widgets.dart` to wait for the app to become active before
-prompting, but none of them imports `material.dart` or builds a widget.
-
-## The cycle
+## How the cycle works
```
load()
@@ -89,7 +114,8 @@ prompting, but none of them imports `material.dart` or builds a widget.
ad closes --> that side's fill is consumed --> load() reloads only what is missing
```
-Details worth knowing:
+Four things in that cycle are easy to get wrong. The code handles all four, and
+they are worth understanding before you adapt it:
- **A `none` result is not a winner.** It is stored as "nothing", so the next
`load()` runs the round again instead of parking on a winner that cannot show.
@@ -104,15 +130,21 @@ Details worth knowing:
`LOAD_NOT_ALLOWED_WHILE_SHOWING`. Destroying leaves the next load to build a
fresh instance, which is also what makes it run a new auction.
-## Required setup
+## Point it at your own app
-1. **Enable Trusted Arbiter for your app in the CloudX dashboard.** No code here
- can turn it on. You can confirm it from the logs at startup:
+The ad unit ids checked in here belong to CloudX's public sample app
+(`io.cloudx.sample`), and the AdMob ids are Google's public test units. Replace
+all of them, and do these five things together:
+
+1. **Ask CloudX to enable Trusted Arbiter for your app.** No code here can turn
+ it on. Confirm it from the logs at startup:
`[InitializationService] Arbiter enabled: https://sdk.cloudx.io/arbitration`.
+ If that line is missing, the arbiter call will not do what this demo shows.
2. **Match your app key to your bundle id.** Bid requests are authorized per app
key AND bundle id. Change `lib/cloudx/demo_config.dart`, the Android
- `applicationId` and the iOS `PRODUCT_BUNDLE_IDENTIFIER` together, or every
- round comes back `NO_FILL[302]` with nothing to say the pairing is why.
+ `applicationId` and the iOS `PRODUCT_BUNDLE_IDENTIFIER` together. Get this
+ wrong and every round comes back `NO_FILL[302]`, with nothing on screen to
+ say the pairing is the reason.
3. **Set the Google Mobile Ads application id natively**, in
`android/app/src/main/AndroidManifest.xml` and `ios/Runner/Info.plist`. The
Google SDK throws at startup when it is absent.
@@ -125,15 +157,25 @@ Details worth knowing:
The prompt is answered while `CloudX SDK` still reads `not initialized`. That
order is the point: initialize first and every request that session goes out
without an IDFA and with `dnt = 1`.
-5. **Set your Signing Team in Xcode** before an iOS device or archive build. The
- project ships with no `DEVELOPMENT_TEAM` on purpose, so signing stays on
- automatic and resolves to your own team.
+5. **Set your Signing Team in Xcode** before an iOS device or archive build, as
+ above.
+
+## Reading the screen
+
+Every status line names the platform it came from, so the screen doubles as the
+diagnostic.
+
+| Line | Meaning |
+|---|---|
+| `CloudX` / `AdMob` | That side's last event: `loading`, `loaded: $`, `load failed: ...`, `showing`, `closed`. A CloudX failure carries the SDK's own name for the code, so a round that did not fill reads `load failed: No ad available. (NO_FILL[302])` rather than a bare number. |
+| `Arbiter` | `ADMOB (2 bids)`, `CLOUDX (2 bids)`, `no winner (1 bid)`, or `failed: ...`. |
+| `Revenue -> CloudX` | The last AdMob paid event forwarded through `reportRevenueData`, and what that call returned. A `true` does not mean the price was kept; a revenue of 0.0 is discarded. |
-> The ad unit ids checked in here belong to the public CloudX sample app
-> (`io.cloudx.sample`), and the AdMob ids are Google's public test units.
-> Replace all of them.
+If `Arbiter` only ever reads `(1 bid)`, one side is not filling. Look at which
+of the two lines above it says `load failed`; the arbiter is working correctly
+either way.
-## Versions
+## Versions and adapters
| Pin | Version | Why |
|---|---|---|
@@ -146,16 +188,16 @@ Details worth knowing:
Dart 3.12 and Flutter 3.44 are the floors, set by `webview_flutter_android` and
`webview_flutter_wkwebview`, which `google_mobile_ads` 9.1.0 pulls in.
+The adapter list in `android/app/build.gradle.kts` and `ios/Podfile` is the full
+CloudX set, so you can see the shape of it. **Take only the networks your
+dashboard actually serves**; each one adds to your binary.
+
**`CloudXGoogleWaterfallAdapter` / `io.cloudx:adapter-googlewaterfall` is
deliberately absent.** It runs AdMob demand *inside* the CloudX auction, which
is the opposite of what this demo shows: here AdMob is an external bid competing
against CloudX through the arbiter. Shipping both would make the two bids the
same demand.
-The adapter list in `android/app/build.gradle.kts` and `ios/Podfile` is the full
-CloudX set. Take only the networks your dashboard actually serves; each one adds
-to your binary.
-
**BIGO is Android only**, which is why the Gradle file lists one network more
than the Podfile. It also needs cleartext traffic to `127.0.0.1`, because the
BIGO Ads SDK serves some creative assets from a loopback server on the device:
@@ -164,28 +206,13 @@ and the `` element references it. Drop the adapter and you can drop
both. See the
[BIGO adapter page](https://docs.cloudx.io/en/android/adapters/bigo/overview).
-## Running
+## Getting help
-```sh
-flutter pub get
-(cd ios && pod install) # iOS only
-flutter run
-```
+Start with the [Flutter integration
+guide](https://docs.cloudx.io/en/flutter/integration), the [Trusted Arbiter
+page](https://docs.cloudx.io/en/flutter/trusted-arbiter), and the
+[changelog](https://docs.cloudx.io/en/flutter/changelog). The plugin itself is
+on pub.dev as [`cloudx_flutter`](https://pub.dev/packages/cloudx_flutter).
-## Reading the screen
-
-Every status line names the platform it came from, so the screen doubles as the
-diagnostic.
-
-
-
-
-| Line | Meaning |
-|---|---|
-| `CloudX` / `AdMob` | That side's last event: `loading`, `loaded: $`, `load failed: ...`, `showing`, `closed`. A CloudX failure carries the SDK's own name for the code, so a round that did not fill reads `load failed: No ad available. (NO_FILL[302])` rather than a bare number. |
-| `Arbiter` | `ADMOB (2 bids)`, `CLOUDX (2 bids)`, `no winner (1 bid)`, or `failed: ...`. |
-| `Revenue -> CloudX` | The last AdMob paid event forwarded through `reportRevenueData`, and what that call returned. A `true` does not mean the price was kept; a revenue of 0.0 is discarded. |
-
-If `Arbiter` only ever reads `(1 bid)`, one side is not filling. Look at which
-of the two lines above it says `load failed`; the arbiter is working correctly
-either way.
+For an app key, ad unit ids, or to have Trusted Arbiter switched on, talk to
+your CloudX contact.