> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudx.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Trusted Arbiter

> Compare CloudX bids with supported third-party bids in Unity games

Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. Available from Unity SDK 4.1.0 (backed by Android SDK 4.1.1 and iOS SDK 3.4.1), it supports CloudX, Unity LevelPlay, and PubMatic bid inputs.

<Info>
  Call `CloudXSdk.Arbiter()` after `CloudXSdk.Initialize()` has completed (after the `CloudXInitializationCallbacks.OnSdkInitializedEvent` event). Until the arbiter service is available, the SDK falls back to selecting the highest comparable USD bid from the supplied inputs.
</Info>

## Basic API

Create bid candidates from loaded ads, then pass them to `CloudXSdk.Arbiter()`.

```csharp theme={null}
using System.Collections.Generic;
using CloudX;
using UnityEngine;

// cloudXAd is the CloudXAd from a CloudX OnAdLoadSuccess callback.
// Its AdValues map carries the trusted-payload keys the server uses to validate the bid.
// levelPlayNetwork / levelPlayRevenue / levelPlayPrecision come from the Unity LevelPlay ad info.
// pubMaticPrice and pubMaticPartner come from the PubMatic/OpenWrap bid object.
var bids = new List<CloudXArbiterBid>
{
    new CloudXArbiterBid.CloudX(cloudXAd),
    new CloudXArbiterBid.LevelPlay(
        NetworkName: levelPlayNetwork,
        Revenue: levelPlayRevenue,
        Precision: levelPlayPrecision),
    new CloudXArbiterBid.PubMatic(
        Price: pubMaticPrice,
        PartnerName: pubMaticPartner),
};

CloudXSdk.Arbiter(bids, result =>
{
    Debug.Log($"Selected platform: {result.Platform}");
});
```

`CloudXArbiterBid.CloudX` accepts the `CloudXAd` object from a CloudX load callback. `CloudXArbiterBid.LevelPlay` accepts Unity LevelPlay ad info values. `CloudXArbiterBid.PubMatic` accepts a PubMatic OpenWrap bid price and optional partner name. The `Extras` map is optional on both the `LevelPlay` and `PubMatic` bids, and `PartnerName` is optional on `PubMatic`. The completion callback runs on the Unity main thread, so you can show an ad or update UI directly from it.

`result.Platform` is `CloudXArbiterPlatform.CloudX`, `LevelPlay`, or `PubMatic` for the selected platform, or `CloudXArbiterPlatform.None` when no winner could be selected (for example, no bids were supplied). The result also exposes `result.Id` (the auction identifier), `result.BidId` (the winning bid identifier, null when the platform is `None`), and `result.Extras` (additional metadata from the winning network).

## Step-by-step: arbitrate CloudX and LevelPlay

This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into `CloudXSdk.Arbiter()`. It uses an interstitial, but the same field mapping applies to any format.

<Steps>
  <Step title="Load both candidates">
    Create the LevelPlay interstitial and start a load on each platform. Subscribe to the load callbacks before loading.

    ```csharp theme={null}
    var levelPlayAd = new LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID");
    levelPlayAd.OnAdLoaded += OnLevelPlayLoaded;
    levelPlayAd.OnAdLoadFailed += OnLevelPlayLoadFailed;
    levelPlayAd.LoadAd();

    CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += OnCloudXLoaded;
    CloudXSdk.LoadInterstitial("YOUR_CLOUDX_AD_UNIT_ID");
    ```
  </Step>

  <Step title="Capture each platform's loaded ad">
    LevelPlay delivers a `LevelPlayAdInfo` in its `OnAdLoaded` callback; CloudX delivers a `CloudXAd` in `OnAdLoadSuccess`. Hold onto both — you read the arbiter inputs from them in the next step.

    ```csharp theme={null}
    private CloudXAd _cloudXAd;
    private LevelPlayAdInfo _levelPlayInfo;

    private void OnCloudXLoaded(CloudXAd ad) => _cloudXAd = ad;
    private void OnLevelPlayLoaded(LevelPlayAdInfo info) => _levelPlayInfo = info;
    ```
  </Step>

  <Step title="Map the values into bids">
    Read the LevelPlay fields off `LevelPlayAdInfo` and pass them to `CloudXArbiterBid.LevelPlay`. The CloudX bid takes the `CloudXAd` directly.

    | `LevelPlayAdInfo` field | Type      | `CloudXArbiterBid.LevelPlay` parameter |
    | ----------------------- | --------- | -------------------------------------- |
    | `adNetwork`             | `string`  | `NetworkName`                          |
    | `revenue`               | `double?` | `Revenue` (coalesce with `?? 0`)       |
    | `precision`             | `string`  | `Precision`                            |

    ```csharp theme={null}
    var bids = new List<CloudXArbiterBid>
    {
        new CloudXArbiterBid.CloudX(_cloudXAd),
        new CloudXArbiterBid.LevelPlay(
            NetworkName: _levelPlayInfo.adNetwork,   // LevelPlayAdInfo.adNetwork
            Revenue: _levelPlayInfo.revenue ?? 0,    // LevelPlayAdInfo.revenue is double?
            Precision: _levelPlayInfo.precision),    // LevelPlayAdInfo.precision
    };
    ```
  </Step>

  <Step title="Run the arbiter">
    Pass the bids to `CloudXSdk.Arbiter()` with a completion callback. The callback runs on the Unity main thread.

    ```csharp theme={null}
    CloudXSdk.Arbiter(bids, OnArbiterCompleted);
    ```
  </Step>

  <Step title="Show the winner">
    Switch on `result.Platform` and show the winning platform's ad. `CloudXArbiterPlatform.None` means no winner was selected — continue without showing an ad.

    ```csharp theme={null}
    private void OnArbiterCompleted(CloudXArbiterResult result)
    {
        switch (result.Platform)
        {
            case CloudXArbiterPlatform.CloudX:
                CloudXSdk.ShowInterstitial("YOUR_CLOUDX_AD_UNIT_ID", "level_complete");
                break;
            case CloudXArbiterPlatform.LevelPlay:
                levelPlayAd.ShowAd("level_complete");
                break;
            default:
                break; // CloudXArbiterPlatform.None — no winner; continue without an ad
        }
    }
    ```
  </Step>
</Steps>

<Note>
  `LevelPlayAdInfo` exposes camelCase fields (`adNetwork`, `revenue`, `precision`) in the LevelPlay Unity SDK 8.x. `revenue` is a `double?`, so coalesce it (`?? 0`) when passing it to the non-nullable `Revenue` parameter.
</Note>

The `ArbiterInterstitialController` below packages these same steps into a reusable component that prepares a winner ahead of the placement.

## Interstitial example

This interstitial example arbitrates between two platforms: CloudX and Unity LevelPlay. Prepare a winner before the placement is reached:

1. Load CloudX and LevelPlay in parallel.
2. Wait until both platforms have loaded or failed.
3. Submit only loaded candidates to Trusted Arbiter.
4. Cache the selected platform.
5. At the placement, show the cached winner immediately.

If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.

```csharp ArbiterInterstitialController.cs theme={null}
using System.Collections.Generic;
using CloudX;
using UnityEngine;

/// <summary>
/// Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
/// instantly when a placement is reached.
///
/// Loads the CloudX and LevelPlay interstitials in parallel, waits until both have
/// finished loading or failing, submits the loaded candidates to CloudXSdk.Arbiter,
/// and caches the selected CloudXArbiterPlatform in _nextWinner.
/// </summary>
public class ArbiterInterstitialController : MonoBehaviour
{
    private const string CloudXAdUnitId = "interstitial_main";

    // Set by the host once the LevelPlay Unity SDK reports a loaded interstitial.
    public string LevelPlayNetwork;
    public double LevelPlayRevenue;
    public string LevelPlayPrecision;

    // Invoked once the arbiter has selected a platform for the next show.
    public System.Action<CloudXArbiterPlatform> OnWinnerPrepared;

    private CloudXAd _cloudXAd;
    private bool _cloudXLoadDone;
    private bool _levelPlayLoaded;
    private bool _levelPlayLoadDone;
    private CloudXArbiterPlatform? _nextWinner;

    private void OnEnable()
    {
        CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += OnCloudXLoaded;
        CloudXAdsCallbacks.Interstitial.OnAdLoadFailed += OnCloudXLoadFailed;
        CloudXAdsCallbacks.Interstitial.OnAdHidden += OnCloudXHidden;
        CloudXAdsCallbacks.Interstitial.OnAdShowFailed += OnCloudXShowFailed;
    }

    private void OnDisable()
    {
        CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess -= OnCloudXLoaded;
        CloudXAdsCallbacks.Interstitial.OnAdLoadFailed -= OnCloudXLoadFailed;
        CloudXAdsCallbacks.Interstitial.OnAdHidden -= OnCloudXHidden;
        CloudXAdsCallbacks.Interstitial.OnAdShowFailed -= OnCloudXShowFailed;
    }

    /// <summary>Starts a load for each platform that does not currently hold a cached ad.</summary>
    public void LoadMissingAds()
    {
        if (_cloudXAd == null) CloudXSdk.LoadInterstitial(CloudXAdUnitId);
        if (!_levelPlayLoaded) LoadLevelPlayInterstitial();
    }

    /// <summary>
    /// Shows the prepared winner, returning true only when a show call was made.
    ///
    /// Returns false when no winner is ready or the cached ad is no longer available, in which
    /// case a fresh load cycle is started.
    /// </summary>
    public bool ShowAtPlacement(string placement)
    {
        switch (_nextWinner)
        {
            case CloudXArbiterPlatform.CloudX:
                return ShowCloudX(placement);
            case CloudXArbiterPlatform.LevelPlay:
                return ShowLevelPlay(placement);
            default:
                return false;
        }
    }

    /// <summary>
    /// Runs the arbiter once both platforms have settled, then caches the winning platform.
    ///
    /// Returns early until both loads complete. If neither platform loaded, it restarts the
    /// load cycle; otherwise it submits the loaded candidates to CloudXSdk.Arbiter.
    /// </summary>
    private void MaybePrepareWinner()
    {
        if (!_cloudXLoadDone || !_levelPlayLoadDone) return;

        if (_cloudXAd == null && !_levelPlayLoaded)
        {
            _cloudXLoadDone = false;
            _levelPlayLoadDone = false;
            LoadMissingAds();
            return;
        }

        var bids = new List<CloudXArbiterBid>();
        if (_cloudXAd != null)
        {
            bids.Add(new CloudXArbiterBid.CloudX(_cloudXAd));
        }

        if (_levelPlayLoaded)
        {
            bids.Add(new CloudXArbiterBid.LevelPlay(
                NetworkName: LevelPlayNetwork,
                Revenue: LevelPlayRevenue,
                Precision: LevelPlayPrecision));
        }

        CloudXSdk.Arbiter(bids, result =>
        {
            _nextWinner = result.Platform;
            OnWinnerPrepared?.Invoke(result.Platform);
        });
    }

    private bool ShowCloudX(string placement)
    {
        if (CloudXSdk.IsInterstitialReady(CloudXAdUnitId))
        {
            CloudXSdk.ShowInterstitial(CloudXAdUnitId, placement);
            return true;
        }

        ClearCloudXAndLoadMissingAds();
        return false;
    }

    private bool ShowLevelPlay(string placement)
    {
        if (IsLevelPlayInterstitialReady())
        {
            ShowLevelPlayInterstitial(placement);
            return true;
        }

        ClearLevelPlayAndLoadMissingAds();
        return false;
    }

    private void OnCloudXLoaded(CloudXAd ad)
    {
        _cloudXAd = ad;
        _cloudXLoadDone = true;
        MaybePrepareWinner();
    }

    private void OnCloudXLoadFailed(string adUnitId, CloudXError error)
    {
        _cloudXAd = null;
        _cloudXLoadDone = true;
        MaybePrepareWinner();
    }

    private void OnCloudXHidden(CloudXAd ad) => ClearCloudXAndLoadMissingAds();

    private void OnCloudXShowFailed(CloudXAd ad, CloudXError error) => ClearCloudXAndLoadMissingAds();

    private void ClearCloudXAndLoadMissingAds()
    {
        _cloudXAd = null;
        _cloudXLoadDone = false;
        _nextWinner = null;
        LoadMissingAds();
    }

    private void ClearLevelPlayAndLoadMissingAds()
    {
        _levelPlayLoaded = false;
        _levelPlayLoadDone = false;
        _nextWinner = null;
        LoadMissingAds();
    }

    /*
     * The following members wrap the LevelPlay Unity SDK. Replace the bodies with calls into
     * the LevelPlay SDK you integrate, and route its load callbacks into the fields above:
     * on load success, set _levelPlayLoaded = true and the LevelPlay* values, then set
     * _levelPlayLoadDone = true and call MaybePrepareWinner(); on load failure, set
     * _levelPlayLoaded = false, _levelPlayLoadDone = true, and call MaybePrepareWinner().
     * On ad close or show failure, call ClearLevelPlayAndLoadMissingAds() to drop the
     * consumed ad and start a fresh cycle.
     */
    private void LoadLevelPlayInterstitial() { /* LevelPlay.LoadInterstitial(...) */ }
    private bool IsLevelPlayInterstitialReady() => _levelPlayLoaded;
    private void ShowLevelPlayInterstitial(string placement) { /* LevelPlay.ShowInterstitial(...) */ }
}
```

`ShowAtPlacement()` returns `true` only when an ad show call was made. Keep the cached LevelPlay candidate values up to date while the LevelPlay ad remains loaded.

For PubMatic OpenWrap, create a third-party bid with `new CloudXArbiterBid.PubMatic(price, partnerName)`. If the arbiter service is unavailable, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.
