Skip to main content
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.
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.

Basic API

Create bid candidates from loaded ads, then pass them to the arbiter.
// 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);
}];
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.
1

Load both candidates

Create the CloudX and LevelPlay interstitials, set their delegates, and start a load on each platform.
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];
2

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.
// 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;
}
3

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 fieldTypeCLXArbiterBid.levelPlay parameter
adNetworkNSString *networkName
revenueNSNumber *revenue (unwrap with .doubleValue)
precisionNSString *precision
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];
4

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.
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
    [self showWinner:result];
}];
5

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.
- (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
}
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.
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.
ArbiterInterstitialController.swift
/// 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.