Skip to content

Opera Ads iOS SDK - Client Bidding (C2S)

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?.load(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 loadWithPlacementId:@"s14198264979520"];
}

- (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: .HIGHER_BID,
                    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:OpAdxLossReasonHigherBid
                                    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: .HIGHER_BID, 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:OpAdxLossReasonHigherBid
                          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?.loadAd(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: .HIGHER_BID, 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?.load(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: .HIGHER_BID, winnerPrice: winnerEcpm, winnerBidder: "Winner")
        }
    }
}

Advanced Topics

Loss Reasons

When notifying a loss, provide accurate loss reason:

Swift:

swift
public enum OpAdxLossReason: Int {
    case INTERNAL_ERROR = 1      // Internal SDK error
    case BELOW_FLOOR = 2         // Bid below floor price
    case HIGHER_BID = 3          // Lost to higher bid
}

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

Objective-C:

objective-c
// Loss reason enum
typedef NS_ENUM(NSInteger, OpAdxLossReason) {
    OpAdxLossReasonInternalError = 1,
    OpAdxLossReasonBelowFloor = 2,
    OpAdxLossReasonHigherBid = 3
};

// Usage
[bid notifyLoseWithLossReason:OpAdxLossReasonHigherBid
                  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: .BELOW_FLOOR,
                    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?.load(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: .HIGHER_BID,
                        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: .HIGHER_BID, 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.9.0 Last Updated: 2026-03-24 SDK Version: 2.9.0