Skip to content

Opera Ads iOS SDK - Client Bidding (C2S)

Which integration is this page for?

This page describes how to run your own auction by calling the Opera Ads SDK directly, so that you can compare an Opera Ads bid against other ad networks yourself.

If you integrate Opera Ads through a mediation platform (TopOn, TradPlus, AppLovin MAX, AdMob or LevelPlay), Opera Ads serves through that platform's waterfall and you do not need any of the APIs on this page. See the Mediation guides instead.

Overview

Client-Side Bidding (C2S), also known as In-App Bidding or Header Bidding, allows you to run real-time auctions directly within your app. This gives you full control over the bidding process and enables you to compare Opera Ads bids with other ad networks in real-time.

Benefits of Client Bidding

  • Real-Time Competition: All ad networks bid simultaneously for each impression
  • Higher Revenue: Increased competition often leads to higher eCPMs
  • Full Control: You manage the auction logic and decide the winner
  • Transparency: See exact bid prices from all networks
  • Reduced Latency: No server-side auction delays

How It Works

┌─────────────────────────────────────────────────────────┐
│  1. Request bids from multiple networks in parallel     │
│     ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│     │ Opera Ads│  │ Network A│  │ Network B│          │
│     │ loadC2S  │  │  load()  │  │  load()  │          │
│     └────┬─────┘  └────┬─────┘  └────┬─────┘          │
│          │             │              │                 │
│          ▼             ▼              ▼                 │
│  2. Compare bid prices (eCPM)                          │
│     Opera: $1.50  │  Network A: $1.20  │  Network B: $1.80 │
│                                                         │
│  3. Notify auction results                             │
│     ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│     │notifyLose│  │notifyLose│  │notifyWin │          │
│     └──────────┘  └──────────┘  └──────────┘          │
│                                                         │
│  4. Show winning ad (Network B)                        │
└─────────────────────────────────────────────────────────┘

Prerequisites

Before implementing client bidding, ensure you have:

  1. Opera Ads SDK Integration

    • SDK version 2.3.0 or higher
    • Basic integration completed
    • Working waterfall ads
  2. Understanding of Auction Logic

    • Familiarity with eCPM comparison
    • Win/loss notification requirements
    • Thread safety considerations
  3. Multiple Ad Networks (optional but recommended)

    • Other ad SDKs integrated (for comparison)
    • Ability to run parallel ad loads

Basic Implementation

Step 1: Load Ads in Parallel

Load Opera Ads with .clientBidding auction type alongside other networks:

Swift Example:

swift
import OpAdxSdk

class BiddingManager {
    private var operaAd: OpAdxInterstitialAd?
    private var competitorAd: CompetitorInterstitialAd?  // Your other network

    func loadAdsForBidding() {
        // Start both ad loads simultaneously
        loadOperaAd()
        loadCompetitorAd()
    }

    private func loadOperaAd() {
        // Create Opera Ads instance with clientBidding auction type
        operaAd = OpAdxInterstitialAd(
            placementId: "s14198264979520",
            auctionType: .clientBidding  // ← Important: Use clientBidding
        )

        let listener = OpAdxInterstitialAdLoadListenerImp(
            onAdLoaded: { [weak self] ad in
                print("Opera ad loaded")
                self?.checkAuctionComplete()
            },
            onAdFailedToLoad: { [weak self] error in
                print("Opera ad failed: \(error.message)")
                self?.checkAuctionComplete()
            }
        )

        operaAd?.loadC2SBid(placementId: "s14198264979520", listener: listener)
    }

    private func loadCompetitorAd() {
        // Load your other network's ad
        competitorAd?.load { [weak self] success in
            print("Competitor ad loaded: \(success)")
            self?.checkAuctionComplete()
        }
    }

    private func checkAuctionComplete() {
        // Check if both ads finished loading
        // Then run auction
    }
}

Objective-C Example:

objective-c
#import <OpAdxSdk/OpAdxSdk.h>

@interface BiddingManager ()
@property (nonatomic, strong) OpAdxInterstitialAdBridge *operaAd;
@property (nonatomic, strong) CompetitorInterstitialAd *competitorAd;
@end

@implementation BiddingManager

- (void)loadAdsForBidding {
    // Start both ad loads simultaneously
    [self loadOperaAd];
    [self loadCompetitorAd];
}

- (void)loadOperaAd {
    // Create Opera Ads instance with clientBidding auction type
    self.operaAd = [[OpAdxInterstitialAdBridge alloc]
        initWithPlacementId:@"s14198264979520"
        auctionType:AdAuctionTypeClientBidding];  // ← Important

    self.operaAd.delegate = self;
    [self.operaAd loadC2SBid];
}

- (void)loadCompetitorAd {
    // Load your other network's ad
    [self.competitorAd loadWithCompletion:^(BOOL success) {
        NSLog(@"Competitor ad loaded: %d", success);
        [self checkAuctionComplete];
    }];
}

@end

Step 2: Compare Bid Prices

Get the eCPM from each ad and compare:

Swift:

swift
func runAuction() {
    var bids: [(network: String, ecpm: Double, ad: Any)] = []

    // Get Opera Ads bid
    if let operaAd = operaAd,
       let bid = operaAd.getBid() {
        let ecpm = bid.getEcpm()
        bids.append((network: "Opera", ecpm: ecpm, ad: operaAd))
        print("Opera Ads bid: $\(ecpm)")
    }

    // Get competitor bid
    if let competitorAd = competitorAd {
        let ecpm = competitorAd.getEcpm()  // Your network's method
        bids.append((network: "Competitor", ecpm: ecpm, ad: competitorAd))
        print("Competitor bid: $\(ecpm)")
    }

    // Sort by eCPM (highest first)
    bids.sort { $0.ecpm > $1.ecpm }

    // Determine winner
    if let winner = bids.first {
        print("Winner: \(winner.network) with $\(winner.ecpm)")
        notifyAuctionResults(bids: bids)
    }
}

Objective-C:

objective-c
- (void)runAuction {
    NSMutableArray<Bid *> *bids = [NSMutableArray array];

    // Get Opera Ads bid
    if (self.operaAd && self.operaAd.getBid) {
        OpAdxAdBid *bid = [self.operaAd getBid];
        double ecpm = [bid getEcpm];
        [bids addObject:[[Bid alloc] initWithNetwork:@"Opera" ecpm:ecpm ad:self.operaAd]];
        NSLog(@"Opera Ads bid: $%.4f", ecpm);
    }

    // Get competitor bid
    if (self.competitorAd) {
        double ecpm = [self.competitorAd getEcpm];
        [bids addObject:[[Bid alloc] initWithNetwork:@"Competitor" ecpm:ecpm ad:self.competitorAd]];
        NSLog(@"Competitor bid: $%.4f", ecpm);
    }

    // Sort by eCPM (highest first)
    [bids sortUsingComparator:^NSComparisonResult(Bid *a, Bid *b) {
        if (a.ecpm > b.ecpm) return NSOrderedAscending;
        if (a.ecpm < b.ecpm) return NSOrderedDescending;
        return NSOrderedSame;
    }];

    // Notify results
    [self notifyAuctionResults:bids];
}

Step 3: Notify Auction Results

Important: You MUST notify all participants of the auction result. Each notification can only be called once per ad.

Swift:

swift
func notifyAuctionResults(bids: [(network: String, ecpm: Double, ad: Any)]) {
    guard let winner = bids.first else { return }

    for (index, bid) in bids.enumerated() {
        if index == 0 {
            // Winner
            if bid.network == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
                // Notify Opera Ads it won
                let secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0
                operaAd.getBid()?.notifyWin(
                    secondPrice: secondPrice,
                    bidderName: bids.count > 1 ? bids[1].network : ""
                )
                print("✅ Opera Ads won - showing ad")
                showOperaAd(operaAd)
            } else {
                print("✅ Competitor won - showing their ad")
                showCompetitorAd()
            }
        } else {
            // Loser
            if bid.network == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
                // Notify Opera Ads it lost
                operaAd.getBid()?.notifyLose(
                    lossReason: .lowerThanHighestPrice,
                    winnerPrice: winner.ecpm,
                    winnerBidder: winner.network
                )
                print("❌ Opera Ads lost to \(winner.network)")
            }
        }
    }
}

Objective-C:

objective-c
- (void)notifyAuctionResults:(NSArray<Bid *> *)bids {
    if (bids.count == 0) return;

    Bid *winner = bids.firstObject;

    for (NSInteger i = 0; i < bids.count; i++) {
        Bid *bid = bids[i];

        if (i == 0) {
            // Winner
            if ([bid.network isEqualToString:@"Opera"]) {
                OpAdxInterstitialAdBridge *operaAd = (OpAdxInterstitialAdBridge *)bid.ad;
                OpAdxAdBid *adBid = [operaAd getBid];

                double secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0;
                NSString *secondBidder = bids.count > 1 ? bids[1].network : @"";

                [adBid notifyWinWithSecondPrice:secondPrice bidderName:secondBidder];
                NSLog(@"✅ Opera Ads won - showing ad");
                [self showOperaAd:operaAd];
            } else {
                NSLog(@"✅ Competitor won - showing their ad");
                [self showCompetitorAd];
            }
        } else {
            // Loser
            if ([bid.network isEqualToString:@"Opera"]) {
                OpAdxInterstitialAdBridge *operaAd = (OpAdxInterstitialAdBridge *)bid.ad;
                OpAdxAdBid *adBid = [operaAd getBid];

                [adBid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
                                    winnerPrice:winner.ecpm
                                   winnerBidder:winner.network];
                NSLog(@"❌ Opera Ads lost to %@", winner.network);
            }
        }
    }
}

Step 4: Show Winning Ad

Swift:

swift
func showOperaAd(_ ad: OpAdxInterstitialAd) {
    guard !ad.isAdInvalidated() else {
        print("Opera ad expired")
        return
    }

    let listener = OpAdxInterstitialAdInteractionListenerImp(
        onAdClicked: {
            print("Opera ad clicked")
        },
        onAdDisplayed: {
            print("Opera ad displayed")
        },
        onAdDismissed: {
            print("Opera ad dismissed")
        },
        onAdFailedToShow: { error in
            print("Opera ad failed to show: \(error.message)")
        }
    )

    ad.show(on: self, listener: listener)
}

Ad Format Implementations

Swift:

swift
import OpAdxSdk

class BannerBiddingManager {
    private var operaBanner: OpAdxBannerAdView?

    func loadBannerForBidding() {
        operaBanner = OpAdxBannerAdView()
        operaBanner?.setPlacementId("s14170965187264")
        operaBanner?.setAdSize(.BANNER_MREC)

        let listener = OpAdxBannerAdListenerImp(
            onAdLoaded: { [weak self] bannerInfo in
                print("Banner loaded for bidding")
                self?.runBannerAuction()
            },
            onAdFailedToLoad: { error in
                print("Banner failed: \(error.message)")
            },
            onAdImpression: {
                print("Banner impression")
            },
            onAdClicked: {
                print("Banner clicked")
            }
        )

        // Load with client bidding mode
        operaBanner?.loadC2SBidAd(listener: listener)
    }

    func runBannerAuction() {
        guard let operaBanner = operaBanner,
              let bid = operaBanner.getBid() else {
            return
        }

        let operaEcpm = bid.getEcpm()
        print("Opera Banner bid: $\(operaEcpm)")

        // Compare with other networks and notify
        if operaWon {
            bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
            showBanner(operaBanner)
        } else {
            bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
        }
    }

    func showBanner(_ banner: OpAdxBannerAdView) {
        // Add banner to view hierarchy
        view.addSubview(banner)
        // ... setup constraints
    }
}

Objective-C:

objective-c
@interface BannerBiddingManager ()
@property (nonatomic, strong) OpAdxBannerAdBridge *operaBanner;
@end

@implementation BannerBiddingManager

- (void)loadBannerForBidding {
    self.operaBanner = [[OpAdxBannerAdBridge alloc]
        initWithPlacementId:@"s14170965187264"
        adSize:OpAdxAdSize.BANNER_MREC];

    self.operaBanner.delegate = self;

    // Load with client bidding mode
    [self.operaBanner loadC2SBidAd];
}

- (void)bannerAdDidLoad:(OpAdxBannerAdBridge *)bannerAd {
    NSLog(@"Banner loaded for bidding");
    [self runBannerAuction];
}

- (void)runBannerAuction {
    OpAdxAdBid *bid = [self.operaBanner getBid];
    if (!bid) return;

    double operaEcpm = [bid getEcpm];
    NSLog(@"Opera Banner bid: $%.4f", operaEcpm);

    // Compare and notify
    if (operaWon) {
        [bid notifyWinWithSecondPrice:secondPlaceEcpm bidderName:@"Competitor"];
        [self showBanner:self.operaBanner];
    } else {
        [bid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
                          winnerPrice:winnerEcpm
                         winnerBidder:@"Winner"];
    }
}

@end

Native Ads

Swift:

swift
import OpAdxSdk

class NativeBiddingManager {
    private var operaNative: OpAdxNativeAd?

    func loadNativeForBidding() {
        operaNative = OpAdxNativeAd(
            placementId: "s14198263063424",
            auctionType: .clientBidding
        )

        let listener = OpAdxNativeAdListenerImp(
            onAdLoaded: { [weak self] ad in
                print("Native ad loaded for bidding")
                self?.runNativeAuction()
            },
            onAdFailedToLoad: { error in
                print("Native ad failed: \(error.message)")
            },
            onAdImpression: {
                print("Native ad impression")
            },
            onAdClicked: {
                print("Native ad clicked")
            }
        )

        operaNative?.loadC2SBid(listener: listener)
    }

    func runNativeAuction() {
        guard let operaNative = operaNative,
              let bid = operaNative.getBid() else {
            return
        }

        let operaEcpm = bid.getEcpm()
        print("Opera Native bid: $\(operaEcpm)")

        // Compare and notify
        if operaWon {
            bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
            renderNativeAd(operaNative)
        } else {
            bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
        }
    }

    func renderNativeAd(_ ad: OpAdxNativeAd) {
        let nativeAdView = OpAdxNativeAdView(frame: CGRect(x: 0, y: 0, width: 300, height: 400))
        nativeAdView.configure(with: ad)

        let rootView = OpAdxNativeAdRootView(root: nativeAdView)
        ad.registerInteractionViews(
            container: rootView,
            interactionViews: nativeAdView.interactionViews,
            adChoicePosition: .topRight
        )

        adContainer.addSubview(nativeAdView)
    }
}

Rewarded Ads

Swift:

swift
import OpAdxSdk

class RewardedBiddingManager {
    private var operaRewarded: OpAdxRewardedAd?

    func loadRewardedForBidding() {
        operaRewarded = OpAdxRewardedAd(
            placementId: "s14198592226752",
            auctionType: .clientBidding
        )

        let listener = OpAdxRewardedAdLoadListenerImp(
            onAdLoaded: { [weak self] ad in
                print("Rewarded ad loaded for bidding")
                self?.runRewardedAuction()
            },
            onAdFailedToLoad: { error in
                print("Rewarded ad failed: \(error.message)")
            }
        )

        operaRewarded?.loadC2SBid(placementId: "s14198592226752", listener: listener)
    }

    func runRewardedAuction() {
        guard let operaRewarded = operaRewarded,
              let bid = operaRewarded.getBid() else {
            return
        }

        let operaEcpm = bid.getEcpm()
        print("Opera Rewarded bid: $\(operaEcpm)")

        // Compare and notify
        if operaWon {
            bid.notifyWin(secondPrice: secondPlaceEcpm, bidderName: "Competitor")
            showRewardedAd(operaRewarded)
        } else {
            bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerEcpm, winnerBidder: "Winner")
        }
    }
}

Advanced Topics

Loss Reasons

When notifying a loss, provide accurate loss reason:

Swift:

swift
public enum LossReason: Int {
    case internalError = 1           // Internal SDK error
    case lowerThanFloorPrice = 100   // Bid below floor price
    case lowerThanHighestPrice = 102 // Lost to higher bid
}

// Usage
bid.notifyLose(
    lossReason: .lowerThanHighestPrice,
    winnerPrice: 1.50,
    winnerBidder: "Network X"
)

Objective-C:

objective-c
// Loss reason enum (bridged from Swift LossReason)
// LossReasonInternalError = 1
// LossReasonLowerThanFloorPrice = 100
// LossReasonLowerThanHighestPrice = 102

// Usage
[bid notifyLoseWithLossReason:LossReasonLowerThanHighestPrice
                  winnerPrice:1.50
                 winnerBidder:@"Network X"];

Second Price Auction

In a second-price auction, the winner pays the second-highest bid price:

Swift:

swift
func runSecondPriceAuction(bids: [(name: String, ecpm: Double)]) {
    let sorted = bids.sorted { $0.ecpm > $1.ecpm }

    if let winner = sorted.first {
        // Winner pays second price (or their bid if no second place)
        let secondPrice = sorted.count > 1 ? sorted[1].ecpm : winner.ecpm

        print("Winner: \(winner.name) at $\(winner.ecpm)")
        print("Pays: $\(secondPrice) (second price)")

        // Notify winner
        if winner.name == "Opera" {
            operaAd?.getBid()?.notifyWin(
                secondPrice: secondPrice,
                bidderName: sorted.count > 1 ? sorted[1].name : ""
            )
        }
    }
}

Floor Pricing

Reject bids below your floor price:

Swift:

swift
let floorPrice = 0.50  // $0.50 minimum eCPM

func runAuctionWithFloor(bids: [(name: String, ecpm: Double, ad: Any)]) {
    var validBids = bids.filter { $0.ecpm >= floorPrice }

    if validBids.isEmpty {
        // No bids meet floor price
        print("No bids above floor price of $\(floorPrice)")

        // Notify all bids they lost due to floor
        for bid in bids {
            if bid.name == "Opera", let ad = bid.ad as? OpAdxInterstitialAd {
                ad.getBid()?.notifyLose(
                    lossReason: .lowerThanFloorPrice,
                    winnerPrice: floorPrice,
                    winnerBidder: "Floor Price"
                )
            }
        }
        return
    }

    // Continue with auction using only valid bids
    validBids.sort { $0.ecpm > $1.ecpm }
    notifyResults(validBids)
}

Timeout Handling

Handle cases where ads don't load in time:

Swift:

swift
class BiddingManager {
    private var auctionTimer: Timer?
    private let auctionTimeout: TimeInterval = 5.0  // 5 seconds

    func startBidding() {
        // Start parallel loads
        loadOperaAd()
        loadCompetitorAd()

        // Set timeout
        auctionTimer = Timer.scheduledTimer(
            withTimeInterval: auctionTimeout,
            repeats: false
        ) { [weak self] _ in
            self?.handleAuctionTimeout()
        }
    }

    func handleAuctionTimeout() {
        print("Auction timeout - running with available bids")
        runAuction()
    }

    func onAdLoaded() {
        // Cancel timeout if all ads loaded
        if allAdsLoaded {
            auctionTimer?.invalidate()
            runAuction()
        }
    }
}

Thread Safety

Important: All AdBid methods must be called on the main thread:

Swift:

swift
func notifyWinner() {
    DispatchQueue.main.async {
        self.operaAd?.getBid()?.notifyWin(
            secondPrice: secondPrice,
            bidderName: "Competitor"
        )
    }
}

Objective-C:

objective-c
- (void)notifyWinner {
    dispatch_async(dispatch_get_main_queue(), ^{
        OpAdxAdBid *bid = [self.operaAd getBid];
        [bid notifyWinWithSecondPrice:secondPrice bidderName:@"Competitor"];
    });
}

Complete Example

Here's a complete implementation of client bidding for interstitial ads:

Swift:

swift
import OpAdxSdk

class CompleteBiddingExample: UIViewController {
    private var operaAd: OpAdxInterstitialAd?
    private var competitorAd: CompetitorInterstitial?

    private var operaLoaded = false
    private var competitorLoaded = false

    // MARK: - Start Bidding

    func startBidding() {
        print("Starting client bidding auction...")
        operaLoaded = false
        competitorLoaded = false

        loadOperaAd()
        loadCompetitorAd()
    }

    // MARK: - Load Opera Ads

    func loadOperaAd() {
        operaAd = OpAdxInterstitialAd(
            placementId: "s14198264979520",
            auctionType: .clientBidding
        )

        let listener = OpAdxInterstitialAdLoadListenerImp(
            onAdLoaded: { [weak self] ad in
                print("✅ Opera ad loaded")
                self?.operaLoaded = true
                self?.checkBiddingComplete()
            },
            onAdFailedToLoad: { [weak self] error in
                print("❌ Opera ad failed: \(error.message)")
                self?.operaLoaded = true  // Still mark as complete
                self?.checkBiddingComplete()
            }
        )

        operaAd?.loadC2SBid(placementId: "s14198264979520", listener: listener)
    }

    // MARK: - Load Competitor

    func loadCompetitorAd() {
        competitorAd = CompetitorInterstitial()
        competitorAd?.load { [weak self] success in
            if success {
                print("✅ Competitor ad loaded")
            } else {
                print("❌ Competitor ad failed")
            }
            self?.competitorLoaded = true
            self?.checkBiddingComplete()
        }
    }

    // MARK: - Run Auction

    func checkBiddingComplete() {
        guard operaLoaded && competitorLoaded else { return }

        print("Both ads finished loading - running auction")
        runAuction()
    }

    func runAuction() {
        var bids: [(name: String, ecpm: Double, ad: Any?)] = []

        // Get Opera bid
        if let operaAd = operaAd, let bid = operaAd.getBid() {
            let ecpm = bid.getEcpm()
            bids.append((name: "Opera", ecpm: ecpm, ad: operaAd))
            print("Opera bid: $\(String(format: "%.4f", ecpm))")
        }

        // Get competitor bid
        if let competitorAd = competitorAd {
            let ecpm = competitorAd.getEcpm()
            bids.append((name: "Competitor", ecpm: ecpm, ad: competitorAd))
            print("Competitor bid: $\(String(format: "%.4f", ecpm))")
        }

        // Check if we have any valid bids
        guard !bids.isEmpty else {
            print("No valid bids - cannot show ad")
            return
        }

        // Sort by eCPM (highest first)
        bids.sort { $0.ecpm > $1.ecpm }

        // Notify all participants
        notifyAuctionResults(bids: bids)
    }

    func notifyAuctionResults(bids: [(name: String, ecpm: Double, ad: Any?)]) {
        guard let winner = bids.first else { return }

        print("🏆 Winner: \(winner.name) with $\(String(format: "%.4f", winner.ecpm))")

        for (index, bid) in bids.enumerated() {
            if index == 0 {
                // Winner notification
                if bid.name == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
                    let secondPrice = bids.count > 1 ? bids[1].ecpm : 0.0
                    let secondBidder = bids.count > 1 ? bids[1].name : ""

                    operaAd.getBid()?.notifyWin(
                        secondPrice: secondPrice,
                        bidderName: secondBidder
                    )

                    print("💰 Opera wins - pays $\(String(format: "%.4f", secondPrice))")
                    showOperaAd(operaAd)
                } else {
                    print("💰 Competitor wins - showing their ad")
                    showCompetitorAd()
                }
            } else {
                // Loser notification
                if bid.name == "Opera", let operaAd = bid.ad as? OpAdxInterstitialAd {
                    operaAd.getBid()?.notifyLose(
                        lossReason: .lowerThanHighestPrice,
                        winnerPrice: winner.ecpm,
                        winnerBidder: winner.name
                    )
                    print("❌ Opera lost to \(winner.name)")
                }
            }
        }
    }

    // MARK: - Show Ads

    func showOperaAd(_ ad: OpAdxInterstitialAd) {
        let listener = OpAdxInterstitialAdInteractionListenerImp(
            onAdClicked: {
                print("Opera ad clicked")
            },
            onAdDisplayed: {
                print("Opera ad displayed")
            },
            onAdDismissed: { [weak self] in
                print("Opera ad dismissed")
                self?.operaAd = nil
            },
            onAdFailedToShow: { error in
                print("Opera ad failed to show: \(error.message)")
            }
        )

        ad.show(on: self, listener: listener)
    }

    func showCompetitorAd() {
        competitorAd?.show(from: self)
    }
}

Best Practices

1. Always Notify Results

swift
// ✅ Good: Always notify win or lose
if won {
    bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
} else {
    bid.notifyLose(lossReason: .lowerThanHighestPrice, winnerPrice: winnerPrice, winnerBidder: winner)
}

// ❌ Bad: Forgetting to notify
if won {
    bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}
// Losers never notified!

2. Call Notifications Only Once

swift
// ✅ Good: Single notification
bid.notifyWin(secondPrice: 1.20, bidderName: "Network A")

// ❌ Bad: Duplicate notifications (will be ignored)
bid.notifyWin(secondPrice: 1.20, bidderName: "Network A")
bid.notifyWin(secondPrice: 1.30, bidderName: "Network B")  // Ignored!

3. Use Main Thread

swift
// ✅ Good: Main thread
DispatchQueue.main.async {
    bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}

// ❌ Bad: Background thread (may cause issues)
DispatchQueue.global().async {
    bid.notifyWin(secondPrice: secondPrice, bidderName: competitor)
}

4. Handle Load Failures

swift
// ✅ Good: Handle failures
onAdFailedToLoad: { [weak self] error in
    print("Opera ad failed: \(error.message)")
    self?.operaLoaded = true  // Mark as complete
    self?.checkBiddingComplete()  // Continue auction
}

// ❌ Bad: Ignore failures
onAdFailedToLoad: { error in
    // Auction never completes if this ad fails!
}

5. Implement Timeouts

swift
// ✅ Good: Set timeout
let timeout = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { _ in
    self.runAuction()  // Run with available bids
}

// ❌ Bad: No timeout (auction may never complete)

Troubleshooting

Issue: getBid() Returns nil

Cause: Ad not loaded or load failed

Solution:

swift
// Always check if ad and bid exist
guard let ad = operaAd,
      let bid = ad.getBid() else {
    print("Ad or bid not available")
    return
}

Issue: Notification Methods Have No Effect

Cause: Called more than once or on wrong thread

Solution:

swift
// Ensure called only once and on main thread
if !notificationSent {
    DispatchQueue.main.async {
        bid.notifyWin(secondPrice: price, bidderName: name)
        self.notificationSent = true
    }
}

Issue: Lower eCPM Than Expected

Cause: Test mode or limited demand

Solution:

  • Ensure useTestAd = false in production
  • Check placement ID is correct
  • Verify ad inventory availability
  • Review floor price settings

Support


Document Version: 2.12.0 Last Updated: 2026-08-03 SDK Version: 2.12.0