> ## 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 iOS apps

Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. CloudX iOS SDK 3.4.0 supports CloudX, Unity LevelPlay, and PubMatic bid inputs.

<Info>
  Call `CloudXCore.shared.arbiter(with:completion:)` after CloudX initialization has completed. 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 the arbiter.

<CodeGroup>
  ```objc Objective-C theme={null}
  // cloudXAd is the CLXAd object from a CloudX load callback.
  // levelPlayAdInfo is the Unity LevelPlay ad info object.
  // pubMaticPrice and pubMaticPartnerName come from the PubMatic/OpenWrap bid object.
  CLXArbiterBid *cloudXBid = [CLXArbiterBid cloudXBidWithAd:cloudXAd];

  CLXArbiterBid *levelPlayBid =
      [CLXArbiterBid levelPlayBidWithNetworkName:levelPlayAdInfo.adNetwork
                                         revenue:levelPlayAdInfo.revenue.doubleValue
                                       precision:levelPlayAdInfo.precision];

  CLXArbiterBid *pubMaticBid =
      [CLXArbiterBid pubMaticBidWithPrice:pubMaticPrice
                              partnerName:pubMaticPartnerName
                                   extras:nil];

  CLXArbiterConfiguration *configuration =
      [CLXArbiterConfiguration configurationWithBids:@[cloudXBid, levelPlayBid, pubMaticBid]];

  [[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
      NSLog(@"Selected platform: %@", result.platform.name);
  }];
  ```

  ```swift Swift theme={null}
  // cloudXAd is the CLXAd object from a CloudX load callback.
  // levelPlayAdInfo is the Unity LevelPlay ad info object.
  // pubMaticPrice and pubMaticPartnerName come from the PubMatic/OpenWrap bid object.
  let cloudXBid = CLXArbiterBid.cloudX(ad: cloudXAd)

  let levelPlayBid = CLXArbiterBid.levelPlay(
      networkName: levelPlayAdInfo.adNetwork,
      revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
      precision: levelPlayAdInfo.precision
  )

  let pubMaticBid = CLXArbiterBid.pubMatic(
      price: pubMaticPrice,
      partnerName: pubMaticPartnerName,
      extras: nil
  )

  let configuration = CLXArbiterConfiguration.configuration(
      bids: [cloudXBid, levelPlayBid, pubMaticBid],
      builderBlock: nil
  )

  CloudXCore.shared.arbiter(with: configuration) { result in
      print("Selected platform: \(result.platform.name)")
  }
  ```
</CodeGroup>

`CLXArbiterBid.cloudX` accepts the `CLXAd` object from a CloudX load callback. `CLXArbiterBid.levelPlay` accepts Unity LevelPlay ad info values. `CLXArbiterBid.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 main thread, so you can show an ad or update UI directly from it.

`result.platform` is `CLXArbiterPlatform.cloudX`, `levelPlay`, or `pubMatic` for the selected platform, or `CLXArbiterPlatform.none` when no winner could be selected (for example, no bids were supplied).

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

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

<Steps>
  <Step title="Load both candidates">
    Create the CloudX and LevelPlay interstitials, set their delegates, and start a load on each platform.

    <CodeGroup>
      ```objc Objective-C theme={null}
      self.cloudXInterstitial = [[CloudXCore shared] createInterstitialWithAdUnitId:@"YOUR_CLOUDX_AD_UNIT_ID"];
      self.cloudXInterstitial.delegate = self;
      [self.cloudXInterstitial load];

      self.levelPlayInterstitial = [[LPMInterstitialAd alloc] initWithAdUnitId:@"YOUR_LEVELPLAY_AD_UNIT_ID"];
      self.levelPlayInterstitial.delegate = self;
      [self.levelPlayInterstitial loadAd];
      ```

      ```swift Swift theme={null}
      cloudXInterstitial = CloudXCore.shared.createInterstitial(adUnitId: "YOUR_CLOUDX_AD_UNIT_ID")
      cloudXInterstitial?.delegate = self
      cloudXInterstitial?.load()

      levelPlayInterstitial = LPMInterstitialAd(adUnitId: "YOUR_LEVELPLAY_AD_UNIT_ID")
      levelPlayInterstitial.setDelegate(self)
      levelPlayInterstitial.loadAd()
      ```
    </CodeGroup>
  </Step>

  <Step title="Capture each platform's loaded ad">
    LevelPlay delivers an `LPMAdInfo` in its load callback; CloudX delivers a `CLXAd`. Hold onto both because you read the arbiter inputs from them in the next step.

    <CodeGroup>
      ```objc Objective-C theme={null}
      // Properties: @property (nonatomic, strong) CLXAd *cloudXAd;
      //             @property (nonatomic, strong) LPMAdInfo *levelPlayInfo;

      // CLXInterstitialDelegate
      - (void)didLoadAd:(CLXAd *)ad {
          self.cloudXAd = ad;
      }

      // LPMInterstitialAdDelegate
      - (void)didLoadAdWithAdInfo:(LPMAdInfo *)adInfo {
          self.levelPlayInfo = adInfo;
      }
      ```

      ```swift Swift theme={null}
      var cloudXAd: CLXAd?
      var levelPlayInfo: LPMAdInfo?

      // CLXInterstitialDelegate
      func didLoad(_ ad: CLXAd) {
          cloudXAd = ad
      }

      // LPMInterstitialAdDelegate
      func didLoadAd(with adInfo: LPMAdInfo) {
          levelPlayInfo = adInfo
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Map the values into bids">
    Read the LevelPlay fields off `LPMAdInfo` and pass them to `CLXArbiterBid.levelPlay`. The CloudX bid takes the `CLXAd` directly. Submit only the platforms that actually loaded.

    | `LPMAdInfo` field | Type         | `CLXArbiterBid.levelPlay` parameter    |
    | ----------------- | ------------ | -------------------------------------- |
    | `adNetwork`       | `NSString *` | `networkName`                          |
    | `revenue`         | `NSNumber *` | `revenue` (unwrap with `.doubleValue`) |
    | `precision`       | `NSString *` | `precision`                            |

    <CodeGroup>
      ```objc Objective-C theme={null}
      NSMutableArray<CLXArbiterBid *> *bids = [NSMutableArray array];

      if (self.cloudXAd) {
          [bids addObject:[CLXArbiterBid cloudXBidWithAd:self.cloudXAd]];
      }

      if (self.levelPlayInfo) {
          CLXArbiterBid *levelPlayBid =
              [CLXArbiterBid levelPlayBidWithNetworkName:self.levelPlayInfo.adNetwork
                                                 revenue:self.levelPlayInfo.revenue.doubleValue
                                               precision:self.levelPlayInfo.precision];
          [bids addObject:levelPlayBid];
      }

      CLXArbiterConfiguration *configuration =
          [CLXArbiterConfiguration configurationWithBids:bids];
      ```

      ```swift Swift theme={null}
      var bids: [CLXArbiterBid] = []

      if let cloudXAd {
          bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
      }

      if let levelPlayInfo {
          bids.append(CLXArbiterBid.levelPlay(
              networkName: levelPlayInfo.adNetwork,
              revenue: levelPlayInfo.revenue?.doubleValue ?? 0,
              precision: levelPlayInfo.precision
          ))
      }

      let configuration = CLXArbiterConfiguration.configuration(
          bids: bids,
          builderBlock: nil
      )
      ```
    </CodeGroup>
  </Step>

  <Step title="Run the arbiter">
    Pass the configuration to the arbiter with a completion handler. Run it only after both interstitials have settled: track each load callback and load failure, then submit only the candidates that loaded. The completion runs on the main thread.

    <CodeGroup>
      ```objc Objective-C theme={null}
      [[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
          [self showWinner:result];
      }];
      ```

      ```swift Swift theme={null}
      CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
          self?.showWinner(result)
      }
      ```
    </CodeGroup>
  </Step>

  <Step title="Show the winner">
    Compare `result.platform.name` against the platform constants and show the winning platform's ad. `CLXArbiterPlatform.none` means no winner was selected, so continue without showing an ad.

    <CodeGroup>
      ```objc Objective-C theme={null}
      - (void)showWinner:(CLXArbiterResult *)result {
          NSString *platform = result.platform.name;
          if ([platform isEqualToString:CLXArbiterPlatform.cloudX.name]) {
              [self.cloudXInterstitial showFromViewController:self];
          } else if ([platform isEqualToString:CLXArbiterPlatform.levelPlay.name]) {
              [self.levelPlayInterstitial showAdWithViewController:self placementName:nil];
          }
          // CLXArbiterPlatform.none: no winner; continue without an ad
      }
      ```

      ```swift Swift theme={null}
      func showWinner(_ result: CLXArbiterResult) {
          switch result.platform.name {
          case CLXArbiterPlatform.cloudX.name:
              cloudXInterstitial?.show(from: self)
          case CLXArbiterPlatform.levelPlay.name:
              levelPlayInterstitial.showAd(viewController: self, placementName: nil)
          default:
              break // CLXArbiterPlatform.none: no winner; continue without an ad
          }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

<Note>
  `LPMAdInfo.revenue` is an `NSNumber` and may be absent, so unwrap it to a `double` before passing it to the non-null `revenue:` parameter: `revenue.doubleValue` in Objective-C, `revenue?.doubleValue ?? 0` in Swift.
</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.

```swift ArbiterInterstitialController.swift theme={null}
/// 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 CloudXCore.shared.arbiter,
/// and caches the selected CLXArbiterPlatform in nextWinner.
final class ArbiterInterstitialController: NSObject {
    protocol Listener: AnyObject {
        /// Called when the arbiter has selected a platform for the next show.
        func arbiterInterstitialController(
            _ controller: ArbiterInterstitialController,
            didPrepareWinner platform: CLXArbiterPlatform
        )
    }

    weak var listener: Listener?

    private let cloudXInterstitial: CLXInterstitial
    private let levelPlayInterstitial: LPMInterstitialAd
    private var cloudXAd: CLXAd?
    private var cloudXLoadDone = false
    private var levelPlayAdInfo: LPMAdInfo?
    private var levelPlayLoadDone = false
    private var nextWinner: CLXArbiterPlatform?

    init(cloudXInterstitial: CLXInterstitial, levelPlayInterstitial: LPMInterstitialAd) {
        self.cloudXInterstitial = cloudXInterstitial
        self.levelPlayInterstitial = levelPlayInterstitial
        super.init()
        self.cloudXInterstitial.delegate = self
        self.levelPlayInterstitial.setDelegate(self)
    }

    /// Starts a load for each platform that does not currently hold a cached ad.
    func loadMissingAds() {
        if cloudXAd == nil { cloudXInterstitial.load() }
        if levelPlayAdInfo == nil { levelPlayInterstitial.loadAd() }
    }

    /// 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.
    func showAtPlacement(from viewController: UIViewController, placementName: String? = nil) -> Bool {
        guard let platformName = nextWinner?.name else { return false }

        if platformName == CLXArbiterPlatform.cloudX.name {
            return showCloudX(from: viewController, placementName: placementName)
        }

        if platformName == CLXArbiterPlatform.levelPlay.name {
            return showLevelPlay(from: viewController, placementName: placementName)
        }

        return false
    }

    /// 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 CloudXCore.shared.arbiter.
    private func maybePrepareWinner() {
        guard cloudXLoadDone, levelPlayLoadDone else { return }

        if cloudXAd == nil && levelPlayAdInfo == nil {
            cloudXLoadDone = false
            levelPlayLoadDone = false
            loadMissingAds()
            return
        }

        var bids: [CLXArbiterBid] = []
        if let cloudXAd {
            bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
        }

        if let levelPlayAdInfo {
            bids.append(CLXArbiterBid.levelPlay(
                networkName: levelPlayAdInfo.adNetwork,
                revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
                precision: levelPlayAdInfo.precision
            ))
        }

        let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
        CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
            guard let self else { return }
            nextWinner = result.platform
            listener?.arbiterInterstitialController(self, didPrepareWinner: result.platform)
        }
    }

    private func showCloudX(from viewController: UIViewController, placementName: String?) -> Bool {
        if cloudXInterstitial.isReady {
            if let placementName {
                cloudXInterstitial.show(from: viewController, placement: placementName, customData: nil)
            } else {
                cloudXInterstitial.show(from: viewController)
            }
            return true
        }

        clearCloudXAndLoadMissingAds()
        return false
    }

    private func showLevelPlay(from viewController: UIViewController, placementName: String?) -> Bool {
        if levelPlayInterstitial.isAdReady() {
            levelPlayInterstitial.showAd(viewController: viewController, placementName: placementName)
            return true
        }

        clearLevelPlayAndLoadMissingAds()
        return false
    }

    private func clearCloudXAndLoadMissingAds() {
        cloudXAd = nil
        cloudXLoadDone = false
        nextWinner = nil
        loadMissingAds()
    }

    private func clearLevelPlayAndLoadMissingAds() {
        levelPlayAdInfo = nil
        levelPlayLoadDone = false
        nextWinner = nil
        loadMissingAds()
    }
}

extension ArbiterInterstitialController: CLXInterstitialDelegate {
    func didLoad(_ ad: CLXAd) {
        cloudXAd = ad
        cloudXLoadDone = true
        maybePrepareWinner()
    }

    func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
        cloudXAd = nil
        cloudXLoadDone = true
        maybePrepareWinner()
    }

    func didDisplay(_ ad: CLXAd) {}

    func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
        clearCloudXAndLoadMissingAds()
    }

    func didHide(_ ad: CLXAd) {
        clearCloudXAndLoadMissingAds()
    }

    func didClick(_ ad: CLXAd) {}
}

extension ArbiterInterstitialController: LPMInterstitialAdDelegate {
    func didLoadAd(with adInfo: LPMAdInfo) {
        levelPlayAdInfo = adInfo
        levelPlayLoadDone = true
        maybePrepareWinner()
    }

    func didFailToLoadAd(withAdUnitId adUnitId: String, error: Error) {
        levelPlayAdInfo = nil
        levelPlayLoadDone = true
        maybePrepareWinner()
    }

    func didChangeAdInfo(_ adInfo: LPMAdInfo) {
        levelPlayAdInfo = adInfo
    }

    func didDisplayAd(with adInfo: LPMAdInfo) {}

    func didFailToDisplayAd(with adInfo: LPMAdInfo, error: Error) {
        clearLevelPlayAndLoadMissingAds()
    }

    func didCloseAd(with adInfo: LPMAdInfo) {
        clearLevelPlayAndLoadMissingAds()
    }

    func didClickAd(with adInfo: LPMAdInfo) {}
}
```

`showAtPlacement(from:placementName:)` returns `true` only when an ad show call was made. `didChangeAdInfo(_:)` keeps the cached LevelPlay candidate up to date while it remains loaded.

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