# Introduction

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your application, enabling new monetization and retention channels throught our Loyalty Platform.


# Getting Started

Select your platform

Select the integration based on your development framework.

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/gCi8kKbJJfAFwoMSthG6" %}
[Create a Publisher Account](/getting-started/create-a-publisher-account)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/hl0PMjIXsFU6WBqpiac1" %}
[Create your App/Site](/getting-started/create-your-app-site)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/694IF28v9DOi3J9TnG5X" %}
[Create an AdUnit](/getting-started/create-an-adunit)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/dWOSP9T7ETVAQvDiWo4w" %}
[Test in Sanbox mode](/getting-started/test-in-sanbox-mode)
{% endcontent-ref %}

{% content-ref url="/pages/45oi6SJrUB8VtccOrojJ" %}
[Android](/android)
{% endcontent-ref %}

{% content-ref url="/pages/WeNCDtiieA2t3jORRODw" %}
[iOS](/ios)
{% endcontent-ref %}

{% content-ref url="/pages/Hy9RMWwu8cUPfaklOFdC" %}
[Unity](/unity)
{% endcontent-ref %}

{% content-ref url="/pages/jrpM0xBwwMWQPHAFyug7" %}
[Flutter](/flutter)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/hJSJCv7lUpPUhMIyuZPh" %}
[iFrame](/iframe)
{% endcontent-ref %}


# Create a Publisher Account

Fill the form and create an account on MAF (MyChips) platform.

Navigate here to create your account: <https://dashboard.maf.ad/Account/Register?type=publisher>

Once created, your account is ready to use.


# Create your App/Site

Connect your Website or App to the Platform in just a few easy steps. First, go to the **Sites** section and click on the **Add a** **New Site** button located in the top-right corner.

<figure><img src="/files/w8QT8xbKU8m7DXXgKBaA" alt=""><figcaption></figcaption></figure>

1. **Enter Site Details**

   **Site Name**:

   Provide a unique name to identify your property in your dashboard.

   **App Store or Website URL**:

   Enter the URL of your website or published app.

   Ensure the URL matches the platform where MyChips is hosted.

   Example: If your site is hosted on a web platform, enter `http://example.com`. Only use the domain, such as `example.com`.
2. **Select Platform**

   Choose the platform where myChips is hosted.
3. **Upload Logo (Optional)**

   You can upload a logo to represent your property in the dashboard.

   Allowed formats: JPG, PNG, GIF.

   Maximum file size: 5 MB.

   The logo will be resized to 120x120px.

<figure><img src="/files/rUI4CR9tDIb7HMAAQ248" alt=""><figcaption></figcaption></figure>

After entering this information, click on the "**Save & Continue**".

You have successfully linked your Website or App to the Platform. Check [this document](/getting-started/create-an-adunit) to add an Ad Unit to your network.


# Create an AdUnit

First, go to **Sites** and find the site you created earlier:

<figure><img src="/files/qQ7eDp8tyAb7qtTwB69G" alt=""><figcaption></figcaption></figure>

Click on the **+ New Adunit** button to open a new pop-up window.

**General Settings**

<figure><img src="/files/XAvbSWREshWx8FR2CP8u" alt=""><figcaption></figcaption></figure>

* **Name**:
  * Enter a unique name to identify this property in your dashboard.
* **Margin**:
  * Indicate the percentage of total revenue that is kept as margin for the publisher. Please note, the **margin** should be **the percentage of revenue that you will keep for yourself from total reward granted, and the remaining percentage will be the reward assigned to the user (e.g if you set 30%, 70% will be assigned to the user and 30% to the publisher)**
* **Sandbox**:

  * Activate the Sandbox mode for testing purposes. During sandbox mode, the offerwall will display mockup data and send postback for test purposes only. No revenue will be counted or granted to users.\ <mark style="background-color:yellow;">All AdUnits created by new publishers will initially be set to Sandbox mode and will require manual approval from our team. Once the first AdUnit is approved, any subsequent AdUnits can be created outside of the Sandbox mode without requiring additional approvals.</mark>

**Whitelabel Settings**

<figure><img src="/files/WwgeqglN48byPnXSTpjT" alt=""><figcaption></figcaption></figure>

* **Whitelabel**:
  * Activate Whitelabel mode to customize the offerwall appearance.
* **Color**:
  * Choose the accent color from the color picker.
* **Cover**:

  * Upload a cover image to customize the offerwall.
  * Allowed size: 2000x560px.
  * Allowed formats: JPG, PNG, GIF.
  * Maximum file size: 5 MB

**Virtual Currency Settings**

<figure><img src="/files/wZk9C5qNiTW5oG1kTL4h" alt=""><figcaption></figcaption></figure>

* **Virtual Currency Icon**:
  * Upload a square icon for the virtual currency.
  * Allowed size: 250x250px.
  * Allowed formats: JPG, PNG, GIF, SVG.
  * Maximum file size: 5 MB.
* **Virtual Currency Name**:
  * Enter the name of the virtual currency in your app.
* **Virtual Currency Ratio**:
  * Set the conversion rate between the virtual currency and 1 USD. It corrisponds to **the redeem rate of your platform** (in other words, how many coins/points a user needs to collect to redeem $1). In that way, our system will calculate the reward to the user in a right way and send it through the postback correctly.
* **Self Managed Currency**:
  * Manage currency inside MyChips. This setting will disable the Virtual Currency.
* **Show Decimal**:
  * Show decimal values on offer cards (e.g., 7.99).
* **Round Up Virtual Currency**:
  * Round up the currency to the closest unit (e.g., 1.7 coins will become 2 coins).
* **Enable Chips**:
  * Enable Chips, a secondary virtual currency handled by MyChips offerwall.

When you’re finished setting up your ad unit, click the **Save** button in the bottom-right corner.

You’ve successfully created your first ad unit. Learn more on how to test your Adunit in the next pages.


# Test in Sanbox mode

## TEST YOUR ADUNIT BEFORE IT GOES LIVE <a href="#betterdocs-entry-title" id="betterdocs-entry-title"></a>

Test your Ad Unit before it goes live by selecting the Sandbox option in the bottom-right corner when creating the Ad Unit:

<figure><img src="/files/aDgPP7C1H6QEisgewP85" alt=""><figcaption></figcaption></figure>

Click on the Preview button near your newly created AdUnit to show a web-preview of the Offerwall.

<figure><img src="/files/HiOIVxyUs0pXowpQCzXJ" alt=""><figcaption></figcaption></figure>

You can use the offer "MAF Test Offer" to test your integration. The offer will grant a reward to the user everytime you click on that.

<figure><img src="/files/3YdJpwWNs6C8IlxYG6Rx" alt="" width="302"><figcaption></figcaption></figure>


# Reward Handling


# Page 1

We have 2 way of reward handling, which you can choice as your preference and covenience

1\.&#x20;

in this method you can handle the reward in the sdk, and you can&#x20;

2.Webhook S2S Postback

receive conversions on their back-end servers and handle the reward to the users.

MyChips can send a postback to your server with reward information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

and you can check the detail guide of webhook s2s rpostback set in the sub page o


# Webhook S2S Postback

A Postback URL (or WebHook) is used by Publishers to receive conversions on their back-end servers and handle the reward to the users.

To set up a Postback URL in our Platform, go to **Settings** section and find the **Postback URL section** on the left.

<figure><img src="https://maf.ad/wp-content/uploads/2023/05/postback2-1-1024x683.png" alt=""><figcaption></figcaption></figure>

Here you can edit your URL and add parameters. Click the **Add Parameter** button to add more.

Make sure your URL is in a valid form:

```
https://yourdomain.com/?user_id={user_id}&payout={payout}...
```

On the right, you’ll see a list of **Macros** that are available to use in your Postback URL.

You can use the "Add Parameter" button to help you creating your Postback URL.

<figure><img src="https://maf.ad/wp-content/uploads/2023/05/postback-list2-1024x683.png" alt=""><figcaption></figcaption></figure>

Once you are done, click on **Save Postback URL**.

#### **Important Notes**

Please ensure the following requirements are met:

1. **HTTP Method Requirement**&#x20;

   All postbacks sent from our system use the **HTTP GET** method. Please ensure that your server accepts **GET** requests.
2. **Parameter Parsing**&#x20;

   Verify that your endpoint correctly parses all parameters in the Postback.
3. **IP Whitelisting**\
   Add all MAF postback IP addresses to your whitelist. In the next section (“Validating the S2S Webhook”) we will explain in detail how to configure this.
4. **Support for CPI Bidding Postbacks**
   * For CPI bidding offers, the publisher is paid only upon install.
   * For all subsequent events, your system must not reject postbacks with:
     * `payout = 0`
     * `user_payout = $X`\
       Ensure your platform supports this type of postback.
5. **Reward Calculation**
   * Do not use the virtual currency parameter (`{user_payout_in_vc}`) to calculate rewards.This parameter is provided for informational purposes only.
   * For accurate reward calculation, you must rely on one of the following parameters:
     * **`user_payout`**
     * **`payout`** – the recommended parameter for reward calculation.
   * Reward conversion logic and final computation must always be handled on your side, based on either the `user_payout` or, preferably, the `payout` parameter.


# Validating the Webhook S2S

To validate a Webhook, simply verify the IP it's coming from in the HTTP header.

Here it is the list of IPs from where we send the Webhooks S2S to publishers. Use these IPs to avoid security issues and validate incoming postbacks.

```
168.63.37.145 
20.54.96.37 
13.70.194.104 
34.146.139.91
34.54.234.115 
34.54.248.253  
34.64.93.62   
34.47.93.43	     
34.84.180.208	 
48.209.163.104  
4.207.193.125    
48.209.162.122
34.140.72.20
```

#### **Securing X-Forwarded-For Header**

When your service is behind a load balancer or reverse proxy, be aware of potential manipulation of the `X-Forwarded-For` header. This header is used to identify the originating IP address of the client connecting to the web server through an HTTP proxy or load balancer.

**Risks:**

* **Header Manipulation**: Attackers can spoof the `X-Forwarded-For` header to bypass IP restrictions.

**Security Measures:**

* **Trusting Proxies**: Only trust headers from known proxies or load balancers. Each cloud platform adds the client IP address at a specific position in the `X-Forwarded-For` chain, which you should consider when validating the IP.
  * **AWS (ELB/ALB)**: AWS puts the true client IP at the beginning of the `X-Forwarded-For` list.
  * **Google Cloud Platform (GCP)**: GCP adds the original client IP at the second-to-last position.
  * **Azure**: Azure load balancers append the real client IP at the last position.
* Make sure to parse this header correctly depending on your cloud provider to avoid accepting a spoofed IP.

**PHP Code Example for AWS:**

```
// Function to get the real client IP when behind AWS ELB/ALB
function get_client_ip() {
    // Check if X-Forwarded-For header exists
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        // Split the X-Forwarded-For header into an array
        $forwarded_ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        
        // The first IP in the list is the real client IP (AWS specific)
        $client_ip = trim($forwarded_ips[0]);
    } else {
        // Fallback to REMOTE_ADDR if X-Forwarded-For is not present
        $client_ip = $_SERVER['REMOTE_ADDR'];
    }

    return $client_ip;
}
```

Like this comment<br>


# Rejected S2S Webhook Postback

## What is the Rejected Report Postback?

A Rejected Postback is a notification sent to external partners when they detect suspicious or fraudulent activities during the user interaction with campaigns or apps. Unlike standard postbacks, which are used to track conversions or user events, rejected postbacks signal that an event has been flagged due to abdonrmal behavior or policy violantions.

Each rejected postback contains a parameter called `{rejected_reason_id}`, which provides the specific reason for flagging the event.

You can configure your rejected postback by using the parameter we provide for the S2S Webhook postback, along with {rejected\_reason\_id}.

An example of a Rejected Postback URL looks like this:

```
https://yourdomain.org/postback?user_id={user_id}&...&payout=0&user_payout=0&rejected_reason_id={rejected_reason_id}
```

**Note:** `payout` and `user_payout` will be always set to 0 for rejected events.

### When a Rejected Report Postback is Triggered

A rejected postback is triggered in cases where an external system identifies behavior that doesn't align with expected norms. Each of these issues is represented by a specific `rejected_reason_id` in the postback.

### How to Set Up a Rejected Report Postback

To set up a Rejected Report Postback URL in our platform, go to the Settings page and navigate to the *Additional Postback (optional)* section located at the bottom left.

Select the option "*Rejected"* , enter the full Rejected Report Postback URL in the input field, and then click the button "*Add+".*

<figure><img src="/files/wj2yLX165rL0aqLkmDj9" alt=""><figcaption></figcaption></figure>

## Rejected Reasons Details

Each postback includes a `rejected_reason_id` parameter, which indicates why the event was flagged. The following table outlines the available rejected reasons and their severity.&#x20;

**Note**: We cannot prescribe specific actions, as enforcement is at your discretion. However, as a reference, here is the policy we apply on our own platforms:

* **High severity (first occurrence):** Immediate user ban
* **Multiple medium severity events within a 7-day period:** User ban
* **Low severity:** Used primarily to inform risk scoring and assess content quality

| ID | Severity |
| -- | -------- |
| 0  | Low      |
| 1  | High     |
| 2  | Medium   |
| 3  | Medium   |
| 4  | Low      |
| 5  | Medium   |
| 6  | Low      |
| 7  | Low      |
| 8  | Medium   |
| 9  | Low      |
| 10 | High     |
| 11 | Medium   |
| 12 | High     |
| 13 | Medium   |
| 14 | High     |


# IAP S2S Webhook Postback

### What is the IAP S2S Webhook Postback?

The In-App Purchase (IAP) Server-to-Server (S2S) Webhook Postback is a notification sent to your server endpoint whenever an in-app purchase event completes. The IAP postback may be sent in addition to a general conversion callback; you can choose to reward those events separately or treat them as higher-value completions.

***

**Standard Parameters**

You can use the standard parameters listed on your [Dashboard’s Settings page.](http://dashboard.maf.ad/Account/Login)

\
**IAP-Specific Parameters**

| Placeholder              | Description                                        |
| ------------------------ | -------------------------------------------------- |
| {event\_value}           | Purchase amount                                    |
| {event\_value\_usd}      | Normalized USD amount (string, two decimal places) |
| {event\_value\_currency} | ISO currency code (normalized, e.g., USD)          |

***

### Example  IAP Postback

An example of an  IAP Postback URL looks like this:

```
https://yourdomain.org/postback?clickid={click_id}&event_name={event_name}&aff_sub1={aff_sub1}&aff_sub2={aff_sub2}&conversion_country={conversion_country}&user_id={user_id}&event_value={event_value}&event_value_currency={event_value_currency}&event_value_usd={event_value_usd}&conversion_time={unix_timestamp}
```

### How to Set Up a IAP S2S Webhook Postback

To set up a IAP S2S Webhook Postback URL in our platform, go to the Settings page and navigate to the *Additional Postback (optional)* section located at the bottom left.

Select the option "*Revenue"* , enter the full IAP S2S Webhook Postback URL in the input field, and then click the button "*Add+".*

<figure><img src="/files/9f2Xrrlw4tQrl5fcJ92v" alt=""><figcaption></figcaption></figure>


# IAA S2S Webhook Postback

### What is the IAA S2S Webhook Postback?

The In-App Advertising (IAA) Server-to-Server (S2S) Webhook Postback is a notification sent to your server endpoint whenever an in-app advertisement is viewed by the user.

All postbacks for IAA events will be sent from IP **34.140.72.20**, so you need to add this one to your whitelist to receive ad revenue postbacks.

***

\
**IAP-Specific Parameters**

| Placeholder              | Description                                        |
| ------------------------ | -------------------------------------------------- |
| {event\_value}           | Advertisement amount                               |
| {event\_value\_usd}      | Normalized USD amount (string, two decimal places) |
| {event\_value\_currency} | ISO currency code (normalized, e.g., USD)          |

***

### Example  IAA Postback

An example of an  IAA Postback URL looks like this:

```
https://yourdomain.org/postback?clickid={click_id}&event_name={event_name}&aff_sub1={aff_sub1}&aff_sub2={aff_sub2}&conversion_country={conversion_country}&user_id={user_id}&event_value={event_value}&event_value_currency={event_value_currency}&event_value_usd={event_value_usd}&conversion_time={unix_timestamp}
```

### How to Set Up a IAA S2S Webhook Postback

To set up a IAA S2S Webhook Postback URL in our platform, go to the Settings page and navigate to the *Additional Postback (optional)* section located at the bottom left.

Select the option "*Ad Revenue"* , enter the full IAA S2S Webhook Postback URL in the input field, and then click the button "*Add+".*

<figure><img src="/files/hxf2L24MUAQeLUAUXYko" alt=""><figcaption></figcaption></figure>


# Deduction S2S Webhook Postback

## **What is the Deduction S2S Webhook Postback?**

The Deduction S2S Webhook Postback is a server-to-server notification sent to publishers immediately after a deduction is confirmed.

***

## **Deduction Postback Content**

#### **Parameters**

You may use any of the standard postback parameters available in your **Dashboard → Settings** page.\
These parameters will be dynamically replaced at delivery time.

#### **Example of a Deduction Postback URL**

```
https://yourdomain.org/postback?clickid={click_id}&event_name={event_name}&payout={payout}&user_payout={user_payout}&user_id={user_id}&postback_id={postback_id}
```

Once configured, the publisher will receive **one deduction postback per deducted conversion**.

***

## **How Deductions Are Sent**

If a fraudulent user completes multiple conversion events, for example:

* Install – $0.50
* Level 5 – $1.00
* Level 10 – $2.00

…and the associated **click\_id** is deducted, the publisher will receive **three separate deduction postbacks**, each containing the corresponding `{payout}` value.

The `{payout}` in the postback is sent as a **positive value**.\
If your internal system requires a negative amount, you may prepend a minus sign directly in the URL configuration, e.g.:

```
payout=-{payout}
```

***

## Relationship with Anti-Fraud System

Our anti-fraud system blocks fraudulent conversions in real time (see **Rejected Postback** for more information).

In most cases, fraudulent conversions are identified and rejected immediately upon occurrence.

However, in some cases, more sophisticated fraud patterns may only be detected at the **post-install event level**.

When this happens:

* The related install and/or associated events will be retroactively deducted.
* A Deduction Postback will be sent for each affected conversion

***

## **How to Set Up a Deduction S2S Webhook Postback**

To configure a Deduction S2S Webhook Postback in your dashboard:

1. Go to **Settings**
2. Scroll to the **Additional Postback (optional)** section at the bottom-left

   <figure><img src="/files/1VpSaQ4I9OTIwbcoUVpS" alt=""><figcaption></figcaption></figure>
3. Select **Detuction** from the dropdown
4. Enter your full Deduction S2S Webhook Postback URL
5. Click **Add+**

Your URL will now be used to notify you of any deducted conversions.


# Billing

Go to the **Payments** section and click on **Edit Profile** in **Profile Details**.

<figure><img src="https://maf.ad/wp-content/uploads/2023/05/edit-profile-details-button3-1024x512.png" alt=""><figcaption></figcaption></figure>

Add your company information in the modal and hit "Save".

<figure><img src="https://maf.ad/wp-content/uploads/2023/05/edit-profile-details-1.png" alt=""><figcaption></figcaption></figure>

You successfully updated your Billing Info.


# Unity

<details>

<summary>Release Note</summary>

### **Version:** 1.2.6 (current)

Date: 2025-10-29

New Feature – Age and Gender Parameters

This version adds support for sending **Age** and **Gender** values through the SDK.

**Usage Example:**

```csharp
// Age
MCOfferwallObject.Instance.SetAge(30);

// Gender
MCOfferwallObject.Instance.SetGender(MCGenderEnum.Male);
```

</details>

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/NrA6dzqs8xnnA8F1dEo3" %}
[Install SDK](/unity/install-sdk)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/paCNFqxbvCszI5hnGiqO" %}
[Reward User](/unity/reward-user)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/LrrqPjajgasn9FJtXQN9" %}
[FAQ](/unity/faq)
{% endcontent-ref %}


# Install SDK

MyChips Offerwall Integration Documentation

This documentation guides you through integrating the MyChips Offerwall into your Unity project. The MyChips Offerwall is a powerful tool for monetizing your game by rewarding users with in-game items or currency in exchange for engaging with advertisements. By following these steps, you'll seamlessly add the Offerwall to your game, enhancing user engagement and potentially increasing your revenue.

### Prerequisites

* Ensure a Unity project is already set up.
* Familiarize yourself with basic Unity operations.
* Have an active MyChips account to access your ad unit ID, essential for integration.
* Unity Version Requirement: Minimum version 2020.3.

### Step 1: Download the Package

First, download the MyChips Offerwall Unity&#x20;

{% file src="/files/YnT4q3vdewvDVhhl2E2K" %}

### Step 2: Import the Package

**After downloading the package, you can import it into your Unity project using one of the following methods:**

1. **Using Unity's Import Package Menu:**
   * Open Unity and load your project.
   * Go to **Assets > Import Package > Custom Package**.
   * Select the downloaded **MyChips Offerwall** package and click **Open**.
   * Ensure all files are selected in the import window, then click **Import**.
2. **Using Drag and Drop:**
   * Simply drag and drop the downloaded package directly into the Unity **Assets** area.

### Step 3: Initial Setup

#### Go to the Very First Scene

Ensure you're in the first scene of your game where you intend to integrate the Offerwall. This is usually the main menu or the initial loading scene.

#### Access MyChips Settings

* Navigate to `Window` > `MyChips Settings` in the Unity editor menu.
* Click `Add Prefab` to add the MyChips Offerwall prefab to your scene.

<figure><img src="https://t20519595.p.clickup-attachments.com/t20519595/8e74947d-dda9-4a85-899e-b9306fe7752b/image.png" alt=""><figcaption></figcaption></figure>

#### Configure the Prefab

Select the created MyChips game object in your scene. In the Inspector window, you'll need to add your Ad Unit ID.

**Ad Unit ID**

To find your Ad Unit ID:

* Log into your MyChips publisher dashboard.
* Navigate to the section where your ad units are listed.
* Copy the Ad Unit ID designated for the Android/iOS platform.

Paste this ID into the corresponding field in the MyChips game object's Inspector window.

<figure><img src="https://t20519595.p.clickup-attachments.com/t20519595/535b626d-ea3f-4703-b204-096ea7c42e4f/image.png" alt=""><figcaption></figcaption></figure>

#### Step 4: Show the Offerwall

To display the Offerwall within your game, use the following code snippet at the point where you want the Offerwall to appear:

```csharp
MCOfferwallObject.Instance.ShowOfferwall();
```

#### Step 5: **(Mandatory)** – Set **Google Advertising ID (Android)** and **Identifier for Advertisers (iOS)**

Improve reward tracking and eCPM performance by passing the Google Advertising ID ([Official documentation](https://developer.android.com/training/articles/ad-id)) for Android devices and the IDFA for iOS devices.

**Android:**

```csharp
MCOfferwallObject.Instance.SetGAID("HERE YOUR GAID");
```

replace "HERE YOUR GAID" with your actual Google Advertising ID variable or value.

**iOS:**

```csharp
MCOfferwallObject.Instance.SetIDFA("HERE YOUR IDFA");
```

Replace "HERE YOUR IDFA" with your actual IDFA variable or value.

#### Step 6: (Optional) Set User ID

If your game implements its own user ID logic, you can set a custom user ID for the Offerwall:

```csharp
MCOfferwallObject.Instance.SetUserId("your_custom_user_id");
```

Replace `"your_custom_user_id"` with your actual user ID variable or value.

If you do not provide a specific user ID, one will be automatically generated.

#### Step 7: (Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```csharp
MCOfferwallObject.Instance.SetAge(30);
```

Replace 30 with your actual user age variable or value (integer).

> 💡 **Note:**
>
> * The value should be an integer (e.g., 18, 25, 30).
> * Expected range is 0–100 (inclusive).

#### Step 8:  (Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```csharp
MCOfferwallObject.Instance.SetGender(MCGenderEnum.Male);
```

Available enum values:

```csharp
MCGenderEnum.Male
MCGenderEnum.Female
MCGenderEnum.Other
```

#### Step 9: (Optional) Set Custom Parameters (`aff_sub1`–`aff_sub5` )

We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom values.

```csharp
MCOfferwallObject.Instance.SetAffSub1("your_custom_value");
MCOfferwallObject.Instance.SetAffSub2("your_custom_value");
MCOfferwallObject.Instance.SetAffSub3("your_custom_value");
MCOfferwallObject.Instance.SetAffSub4("your_custom_value");
MCOfferwallObject.Instance.SetAffSub5("your_custom_value");
```

Replace `"your_custom_value"` with your actual custom value.


# Reward User

There are two options for handling bonuses rewarded through the Offerwall:

#### 1. Fully Managed by MyChips

Attach the `UnityEvent` `OnRewardReceived` to your GUI element (very first scene). Within this event, implement the logic to credit the user with the bonus. You will have access to the value of the bonus, allowing you to adjust the reward accordingly.

```csharp
void Start()
{
    MCOfferwallObject.Instance.OnRewardReceived.AddListener(HandleRewardReceived);
}

private void HandleRewardReceived(RewardDTO reward)
{
   // Add your logic here to handle the reward, using the      
  reward.GetRewardInVirtualCurrency()          
}
```

<figure><img src="/files/fzK3ra0Ol5PYOePhulZm" alt=""><figcaption></figcaption></figure>

#### 2. Server-to-Server (S2S) Postbacks

#### If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.         &#x20;

#### &#x20;If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# FAQ

## How to Fix - Dependencie Issue

Handle the duplicate dependencies iussue from UniVewview Settings if the project don't compile

<figure><img src="https://t20519595.p.clickup-attachments.com/t20519595/556ee566-0126-46de-893e-e5bc060dcb44/image.png" alt=""><figcaption></figcaption></figure>


# Android

<details>

<summary>Release Note</summary>

### **Version:** 1.2.0 (current)

Date: 2026-04-24

**New Feature**&#x20;

* SDK - Native

Added new customization options for the Android Native SDK, including configurable scrolling, in-app campaign details, custom icons, text colors, loading views, click handling, and loading lifecycle callbacks.&#x20;

</details>

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/K3hJYZvloKWmtTx1zeyd" %}
[Install SDK](/android/install-sdk)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/NIR6mTe8bSoZSNt0VEtL" %}
[Reward User](/android/reward-user)
{% endcontent-ref %}


# Install SDK

## **1. Introduction** <a href="#h-1-introduction" id="h-1-introduction"></a>

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your Android application, enabling the display of an engaging offerwall.\
\
Once installed, you can choose between two integration options:

* **Offerwall** — A full-screen WebView experience managed by the SDK. See [Offerwall Integration →](/android/sdk-offerwall)
* **Native Campaigns** — A customizable campaign list that you embed directly in your app's UI. See [Native Integration →](/android/sdk-native)

Both options use the same SDK. Install it once, then pick the integration that fits your app.

## **2. Prerequisites** <a href="#h-2-prerequisites" id="h-2-prerequisites"></a>

* Android Studio
* Minimum version requirement **27**.

## **3. SDK Integration** <a href="#h-3-sdk-integration" id="h-3-sdk-integration"></a>

**3.1 Adding the SDK**

{% tabs %}
{% tab title="Kotlin DSL" %}

```kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        maven {
            url = uri("https://europe-west1-maven.pkg.dev/mychips-b31fe/mychips-android-sdk")
        }
    }
}
```

{% endtab %}

{% tab title="Groovy" %}

```groovy
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven("https://europe-west1-maven.pkg.dev/mychips-b31fe/mychips-android-sdk")
    }
}
```

{% endtab %}
{% endtabs %}

> **For Kotlin DSL Projects:** In your project-level `setting.gradle.kts`

> **For Groovy-Based Projects:** In your project-level `build.gradle`

**3.2 Adding the SDK Dependency to App-Level Build File**

In your app-level `build.gradle(Module :app)` file, add the following dependency

{% tabs %}
{% tab title="Kotlin DSL" %}

```
dependencies { 
  implementation("io.mychips:offerwall:+") 
  // Other dependencies...
}
```

{% endtab %}

{% tab title="Groovy" %}

```groovy
dependencies {
  implementation 'io.mychips:offerwall:+' 
  // Other dependencies... 
}
```

{% endtab %}
{% endtabs %}

**3.3 Configuring the Android Manifest**

In your `AndroidManifest.xml`, add the following:

**Permission for Internet Access:**

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

> If you plan to use the **Offerwall** integration, you must also register the offerwall activity. See [Offerwall Integration](https://docs.mychips.io/android/pages/tJ06YL0OX03BBfpq2br1#id-1.-manifest-configuration).

## **4. Initializing the SDK**&#x20;

In your main activity’s `onCreate` method, import and initialize the SDK:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
import io.mychips.offerwall.MCOfferwallSDK
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Replace 'YOUR_API_KEY'
        MCOfferwallSDK.Init(this, "YOUR_API_KEY") 
        
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
import io.mychips.offerwall.sdk.MCOfferwallSDK;
// ...public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Replace 'YOUR_API_KEY' 
        MCOfferwallSDK.Init(this,"YOUR_API_KEY");
        
    }
}
```

{% endtab %}
{% endtabs %}

> Obtain your API key and User ID from[ **Universal Developer Portal**](https://dashboard.maf.ad/)

#### 4.1 (Mandatory) Set Google AdvertisingId

Improve reward tracking and eCPM performance by passing the advertising id. [Official documentation](https://developer.android.com/training/articles/ad-id)

```java
MCOfferwallSDK.SetAdvertisingId("HERE YOUR Google Advertising ID");
```

#### 4.2(Optional) Set UserId if you have your own unique id

```java
  MCOfferwallSDK.SetUserId("HERE YOUR USER ID");
```

Replace "HERE YOUR USER ID" with your actual user ID variable or value.

If you do not provide a specific user ID, one will be automatically generated.

#### 4.3(Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.SetAge(30);
```

Replace 30 with your actual user age variable or value (integer).

💡 **Note:**

* The value should be an integer (e.g., 18, 25, 30).
* Expected range is 0–100 (inclusive).

#### 4.4(Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.SetGender(MCGenderEnum.FEMALE);
```

Available enum values:

```java
  MCGenderEnum.MALE
  MCGenderEnum.FEMALE
  MCGenderEnum.OTHER
```

#### 4.5 (Optional) Set Custom Parameters (`aff_sub1`–`aff_sub5` )

We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom values.

```kotlin
MCOfferwallSDK.setAffSub1("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub2("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub3("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub4("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub5("HERE YOUR CUSTOM VALUE");
```

Replace "HERE YOUR CUSTOM VALUE" with your actual custom value.

## **5. Next Steps**

Choose your integration:

<table><thead><tr><th width="178">Integration</th><th width="308">Description</th><th>Guide</th></tr></thead><tbody><tr><td><strong>Offerwall</strong></td><td>Full-screen WebView experience</td><td><a href="/pages/tJ06YL0OX03BBfpq2br1">Offerwall Integration →</a></td></tr><tr><td><strong>Native Campaigns</strong></td><td>Customizable campaign list in your UI</td><td><a href="/pages/Thv8MCuBvnLgyuyt7sbe">Native Quick Start →</a></td></tr></tbody></table>


# SDK - Offerwall

## Offerwall Integration

> **Prerequisite:** Complete the [SDK Installation](/android/install-sdk) first.

The Offerwall displays a full-screen WebView with all available campaigns. The SDK manages the entire UI — you just launch it.

### **1. Manifest Configuration**

Add the Offerwall activity to your `AndroidManifest.xml`:

```xml
<activity android:name="io.mychips.offerwall.controller.MCOfferwallActivity"
          android:theme="@style/Theme.AppCompat.NoActionBar"/>
```

### **2. Display the Offerwall**

{% tabs %}
{% tab title="Kotlin" %}

<pre class="language-kotlin"><code class="lang-kotlin"><strong>import io.mychips.offerwall.controller.MCOfferwallController
</strong>
val mc = MCOfferwallController(this)
mc.Show("AD_UNIT_ID")
</code></pre>

{% endtab %}

{% tab title="Java" %}

```java
import io.mychips.offerwall.controller.MCOfferwallController;

MCOfferwallController mc = new MCOfferwallController(this);
mc.Show("AD_UNIT_ID");
```

{% endtab %}
{% endtabs %}

> Replace `AD_UNIT_ID` with your Ad unit ID from the [Developer Portal](https://dashboard.maf.ad/).

### **3. (Optional) Customize Toolbar Title**

```java
MCOfferwallSDK.SetToolbarTitle("My Rewards");
```

If no title is set, the toolbar remains blank.


# SDK - Native

## Quick Start

> **Prerequisite:** Complete the [SDK Installation](/android/install-sdk) first. Minimum SDK version required: 1.2.1.

Native Campaigns lets you display a scrollable list of campaigns directly in your app's UI. The SDK provides a default layout — you just drop it in and call `load()`.

<figure><img src="/files/lZQWWLxArG2F8cHrVbXJ" alt=""><figcaption></figcaption></figure>

***

### **1. Set the Ad Unit ID**

After `Init`, set the ad unit ID for native campaigns:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
MCOfferwallSDK.Init(this, "YOUR_API_KEY")
MCOfferwallSDK.SetUserId("YOUR_USER_ID")
MCOfferwallSDK.SetAdunitId("YOUR_AD_UNIT_ID")
```

{% endtab %}

{% tab title="Java" %}

```java
MCOfferwallSDK.Init(this, "YOUR_API_KEY");
MCOfferwallSDK.SetUserId("YOUR_USER_ID");
MCOfferwallSDK.SetAdunitId("YOUR_AD_UNIT_ID");
```

{% endtab %}
{% endtabs %}

> Get your Ad unit ID from the [Developer Portal](https://dashboard.maf.ad/).

### **2. Add the View to Your Layout**

In your layout XML, add the `MCNativeAdView`:

```xml
<io.mychips.nativesdk.view.MCNativeAdView
    android:id="@+id/mc_adView"
    android:layout_width="match_parent"
    android:layout_height="180dp" />
```

### **3. Load Campaigns**

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
import io.mychips.nativesdk.view.MCNativeAdView

val adView = findViewById<MCNativeAdView>(R.id.mc_adView)
adView.load()
```

{% endtab %}

{% tab title="Java" %}

```
import io.mychips.nativesdk.view.MCNativeAdView;

MCNativeAdView adView = findViewById(R.id.mc_adView);
adView.load();
```

{% endtab %}
{% endtabs %}

**That's it.** The SDK handles everything:

* Fetches campaigns from the API
* Shows a loading skeleton while fetching
* Displays campaigns in a horizontal scrollable list
* Tracks impressions automatically
* Opens the campaign detail page on click

***

### 4. (Optional) Open Campaign Details In-App

By default, tapping a campaign opens the detail page in the device's external browser. To keep users inside your app using an in-app WebView:

```java
MCOfferwallSDK.SetOpenInApp(true);
```

If you enable this, you must also register the WebView activity in your AndroidManifest.xml:

```xml
<activity android:name="io.mychips.offerwall.controller.MCOfferwallActivity"
            android:theme="@style/Theme.AppCompat.NoActionBar"/>
```

Since this is the activity defined in the [SDK - Offerwall](/android/sdk-offerwall), you can refer to its documentation if you want to set a title for it:

```java
MCOfferwallSDK.SetToolbarTitle("My Rewards");
```

### 5. What Happens Behind the Scenes

When you call `adView.load()`, the SDK:

1. Shows a **pulsing skeleton placeholder** that matches the layout direction
2. Calls the API to fetch campaigns
3. Replaces the skeleton with the **campaign list**
4. **Fires impression pixels** automatically when each campaign becomes visible
5. **Opens the campaign detail page** in the browser when the user taps a campaign

No manual tracking or click handling is needed.

***

### **Next Steps**

* Want to customize the look and feel with small effort? See [Customizations →](https://sites.gitbook.com/preview/site_KwL4X/~/revisions/BN8QqwKMZ6zGLtzh4VqC/android/sdk-native/sdk-native-custom-layout)
* Need a completely different card design? See [Custom Layouts →](https://sites.gitbook.com/preview/site_KwL4X/~/revisions/BN8QqwKMZ6zGLtzh4VqC/android/sdk-native/sdk-native-custom-layout)
* Need the full data reference? See [Data Reference →](https://sites.gitbook.com/preview/site_KwL4X/~/revisions/BN8QqwKMZ6zGLtzh4VqC/android/sdk-native/sdk-native-data-reference)


# SDK Native - Customizations

## Native SDK — Customizations

> **Prerequisite:** Complete the [Native SDK - Quick Start](/android/sdk-native) first.

These customizations require **no custom layout XML** — you configure the built-in default renderer or extend it with small tweaks.

***

### 1. Configuration Options

#### 1.1 Scroll Direction

```java
// Horizontal (default)
adView.setOrientation(RecyclerView.HORIZONTAL);

// Vertical list
adView.setOrientation(RecyclerView.VERTICAL);
```

<figure><img src="/files/TcozUKbx7iiatCVbNGej" alt=""><figcaption></figcaption></figure>

#### 1.2 Maximum Number of Campaigns

```java
adView.setMaxCampaigns(5);   // Show at most 5 campaigns
adView.setMaxCampaigns(10);  // Show all (default)
```

#### 1.3 Nested Scrolling

If your `MCNativeAdView` is not inside a `ScrollView`, you can disable nested scrolling (enabled by default):

```java
adView.setNestedScrollingEnabled(false);
```

#### 1.4 Open Campaign Details In-App

By default, tapping a campaign opens in the device's external browser. To keep users inside your app:

```java
MCOfferwallSDK.SetOpenInApp(true);
```

If you enable this, register the WebView activity in your `AndroidManifest.xml`:

```xml
<activity android:name="io.mychips.offerwall.controller.MCOfferwallActivity"
          android:theme="@style/Theme.AppCompat.NoActionBar"/>
```

***

### 2. Change the Currency Icon

The default renderer shows a coin icon next to the reward value.

<figure><img src="/files/WBxFE9Zx9rprSq0xBCin" alt=""><figcaption></figcaption></figure>

You can replace it in two ways:

#### Option A: Set a custom URL

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
val renderer = MCDefaultAdRenderer()
renderer.setCurrencyIconUrl("https://example.com/my_coin.png")
adView.setRenderer(renderer)
adView.load()
```

{% endtab %}

{% tab title="Java" %}

```java
MCDefaultAdRenderer renderer = new MCDefaultAdRenderer();
renderer.setCurrencyIconUrl("https://example.com/my_coin.png");
adView.setRenderer(renderer);
adView.load();
```

{% endtab %}
{% endtabs %}

#### Option B: Use a local drawable

To use a drawable from your app's resources, disable the URL-based icon and set it in `onBindCampaign`:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
val renderer = object : MCDefaultAdRenderer() {
    override fun onBindCampaign(itemView: View, campaign: MCCampaign, position: Int) {
        super.onBindCampaign(itemView, campaign, position)

        val ivCurrency = itemView.findViewById<ImageView>(
            io.mychips.offerwall.R.id.mc_iv_currency)
        ivCurrency?.setImageResource(R.drawable.my_currency_icon)
    }
}
renderer.setCurrencyIconUrl("")  // disable default CDN icon
adView.setRenderer(renderer)
adView.load()
```

{% endtab %}

{% tab title="Java" %}

```java
MCDefaultAdRenderer renderer = new MCDefaultAdRenderer() {
    @Override
    public void onBindCampaign(View itemView, MCCampaign campaign, int position) {
        super.onBindCampaign(itemView, campaign, position);

        ImageView ivCurrency = itemView.findViewById(
            io.mychips.offerwall.R.id.mc_iv_currency);
        if (ivCurrency != null) {
            ivCurrency.setImageResource(R.drawable.my_currency_icon);
        }
    }
};
renderer.setCurrencyIconUrl("");  // disable default CDN icon
adView.setRenderer(renderer);
adView.load();
```

{% endtab %}
{% endtabs %}

> **Important:** Call `setCurrencyIconUrl("")` when using a local drawable. This prevents the default CDN icon from loading asynchronously and overwriting your resource.

***

### 3. Customize Text Colors

Override `onBindCampaign` and change colors after the default binding:

```java
MCDefaultAdRenderer renderer = new MCDefaultAdRenderer() {
    @Override
    public void onBindCampaign(View itemView, MCCampaign campaign, int position) {
        super.onBindCampaign(itemView, campaign, position);

        TextView tvReward = itemView.findViewById(
            io.mychips.offerwall.R.id.mc_tv_reward);
        if (tvReward != null) {
            tvReward.setTextColor(Color.parseColor("#FF1976D2")); // Blue
        }
    }
};
adView.setRenderer(renderer);
```

#### Default Layout View IDs

<table><thead><tr><th width="433">View ID</th><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>io.mychips.offerwall.R.id.mc_iv_thumbnail</code></td><td><code>ImageView</code></td><td>Campaign image</td></tr><tr><td><code>io.mychips.offerwall.R.id.mc_tv_name</code></td><td><code>TextView</code></td><td>Campaign name</td></tr><tr><td><code>io.mychips.offerwall.R.id.mc_tv_reward</code></td><td><code>TextView</code></td><td>Reward value</td></tr><tr><td><code>io.mychips.offerwall.R.id.mc_iv_currency</code></td><td><code>ImageView</code></td><td>Currency icon</td></tr><tr><td><code>io.mychips.offerwall.R.id.mc_tv_badge_promo</code></td><td><code>TextView</code></td><td>Promo badge</td></tr><tr><td><code>io.mychips.offerwall.R.id.mc_tv_badge_progress</code></td><td><code>TextView</code></td><td>In Progress badge</td></tr></tbody></table>

***

### 4. Custom Loading View

By default, the SDK shows a pulsing skeleton placeholder while loading.

<figure><img src="/files/YrPjuCW3E9K3zWCeb18u" alt=""><figcaption></figcaption></figure>

You can replace it:

```kotlin
// Simple spinner
val spinner = ProgressBar(this)
adView.setLoadingView(spinner)
```

Or doing something more complex with text as well:

```java
// Spinner + text
LinearLayout loading = new LinearLayout(this);
loading.setOrientation(LinearLayout.VERTICAL);
loading.setGravity(Gravity.CENTER);

ProgressBar spinner = new ProgressBar(this);
loading.addView(spinner);

TextView text = new TextView(this);
text.setText("Loading offers...");
text.setGravity(Gravity.CENTER);
loading.addView(text);

adView.setLoadingView(loading);
```

Pass `null` to restore the default skeleton:

```java
adView.setLoadingView(null);
```

<figure><img src="/files/fHFsG0SKJStbMAPtb7Yr" alt=""><figcaption></figcaption></figure>

***

### 5. Custom Click Handler

Override the default click behavior per-view:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
adView.setOnCampaignClickListener { campaign, position ->
    // Your custom logic: show a dialog, navigate, etc.
    MCOfferwallSDK.OnClick(campaign)  // or handle it yourself
}
```

{% endtab %}

{% tab title="Java" %}

```java
adView.setOnCampaignClickListener((campaign, position) -> {
    // Your custom logic: show a dialog, navigate, etc.
    MCOfferwallSDK.OnClick(campaign); // or handle it yourself
});
```

{% endtab %}
{% endtabs %}

> **Tip**: You can configure the default click behavior globally without writing a custom click handler. Call `MCOfferwallSDK.SetOpenInApp(true)` to open campaign details in an in-app WebView instead of the> &#x20;external browser. See the [Open Campaign Details In-App](https://docs.mychips.io/android/sdk-native/pages/Thv8MCuBvnLgyuyt7sbe#id-4.-optional-open-campaign-details-in-app).

***

### 6. Loading Lifecycle Listener

Monitor loading state for your own UI logic:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
adView.setLoadingListener(object : MCNativeAdView.LoadingListener {
    override fun onLoadingStarted() { }
    override fun onCampaignsLoaded(count: Int) { }
    override fun onError(e: Exception) { }
})
```

{% endtab %}

{% tab title="Java" %}

```java
adView.setLoadingListener(new MCNativeAdView.LoadingListener() {
    @Override
    public void onLoadingStarted() { }

    @Override
    public void onCampaignsLoaded(int count) { }

    @Override
    public void onError(Exception e) { }
});
```

{% endtab %}
{% endtabs %}

***

### Next Steps

* Need a completely different card design? See [Custom Layouts →](/android/sdk-native/sdk-native-custom-layout)
* Need the full data reference? See [Data Reference →](/android/sdk-native/sdk-native-data-reference)


# SDK Native - Custom Layout

## Native SDK — Custom Layouts

> **Prerequisite:** Familiar with the [Customizations](/android/sdk-native/sdk-native-customizations) options.

When the default layout doesn't fit your design, you can provide a completely custom XML layout. You control every pixel — the SDK only handles data fetching, impression tracking, and click handling.

***

### How It Works

You provide an implementation of  `MCNativeAdRenderer` — an interface with two methods:

| Method                                  | What it does                                                       |
| --------------------------------------- | ------------------------------------------------------------------ |
| `getItemLayoutId()`                     | Returns your custom XML layout resource for a single campaign card |
| `onBindCampaign(View, MCCampaign, int)` | Binds campaign data to your views — you decide what to show        |

The SDK handles everything else: fetching, scrolling, impression tracking, click handling.

The SDK inflates your layout for each campaign and calls `onBindCampaign` on the UI thread.

Here we present three different custom layout examples that you can use as an inspiration for your implementation. These are just examples, you can customize the layout however you'd like.

***

### Example A: Circular Thumbnails

This example creates a horizontal scroll of campaigns with circular images, promo badges, in-progress indicators, and a currency icon.

<figure><img src="/files/ANy8BzD1sQrpe2C3MZOo" alt=""><figcaption></figcaption></figure>

#### Step A.1: Create the item layout

```xml
<!-- res/layout/item_campaign_circular.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="140dp"
    android:layout_height="wrap_content"
    android:minHeight="222dp"
    android:layout_marginEnd="24dp"
    android:orientation="vertical"
    android:clipChildren="false"
    android:clipToPadding="false">

    <!-- Circular thumbnail + promo badge -->
    <FrameLayout
        android:layout_width="140dp"
        android:layout_height="wrap_content"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingTop="12dp">

        <!-- Circular image -->
        <ImageView
            android:id="@+id/mc_ivIcon"
            android:layout_width="140dp"
            android:layout_height="140dp"
            android:scaleType="centerCrop"
            android:background="@drawable/bg_circle"
            android:clipToOutline="true" />

        <!-- Promo badge (top-left, overlapping) -->
        <TextView
            android:id="@+id/mc_tvPromo"
            android:layout_width="wrap_content"
            android:layout_height="25dp"
            android:layout_gravity="top|start"
            android:layout_marginTop="-12dp"
            android:background="@drawable/mc_bg_badge_promo"
            android:gravity="center"
            android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:textColor="#FFFFFFFF"
            android:textSize="12sp"
            android:textStyle="bold"
            android:visibility="gone" />

    </FrameLayout>

    <!-- Campaign name -->
    <TextView
        android:id="@+id/mc_tvName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:ellipsize="end"
        android:maxLines="2"
        android:fontFamily="sans-serif-medium"
        android:textColor="?android:attr/textColorPrimary"
        android:textSize="14sp" />

    <!-- Currency icon + reward value -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <ImageView
            android:id="@+id/mc_ivCurrency"
            android:layout_width="14dp"
            android:layout_height="14dp"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/mc_tvReward"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="3dp"
            android:textColor="?android:attr/textColorPrimary"
            android:textSize="16sp"
            android:textStyle="bold" />

    </LinearLayout>

    <!-- In Progress badge -->
    <TextView
        android:id="@+id/mc_tvProgress"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        android:background="@drawable/mc_bg_badge_progress"
        android:paddingStart="8dp"
        android:paddingEnd="8dp"
        android:paddingTop="2dp"
        android:paddingBottom="2dp"
        android:text="In Progress"
        android:textColor="#FF424B5A"
        android:textSize="12sp"
        android:visibility="invisible" />

</LinearLayout>
```

You'll also need these drawables:

**`res/drawable/bg_circle.xml`** — circular background for the image:

```xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#FFE8E8E8" />
</shape>
```

> **Note:** The SDK bundles `mc_bg_badge_promo` and `mc_bg_badge_progress` drawables. You can reference them directly from your layouts using `@drawable/mc_bg_badge_promo` and `@drawable/mc_bg_badge_progress`.

#### Step A.2: Set the renderer

You can just copy-paste this code to try it out, but remember to replace `https://my-cdn/my-coin-image.png` with your actual icon:

{% tabs %}
{% tab title="Kotlin" %}

```kotlin
val adView = findViewById<MCNativeAdView>(R.id.adView)

adView.setRenderer(object : MCNativeAdRenderer {
    override fun getItemLayoutId() = R.layout.item_campaign_circular

    override fun onBindCampaign(itemView: View, campaign: MCCampaign, position: Int) {
        val tvName = itemView.findViewById<TextView>(R.id.mc_tvName)
        val tvReward = itemView.findViewById<TextView>(R.id.mc_tvReward)
        val ivIcon = itemView.findViewById<ImageView>(R.id.mc_ivIcon)
        val ivCurrency = itemView.findViewById<ImageView>(R.id.mc_ivCurrency)
        val tvPromo = itemView.findViewById<TextView>(R.id.mc_tvPromo)
        val tvProgress = itemView.findViewById<TextView>(R.id.mc_tvProgress)

        tvName.text = campaign.name

        // Locale-formatted reward
        val nf = java.text.NumberFormat.getNumberInstance()
        nf.maximumFractionDigits = 0
        tvReward.text = nf.format(campaign.totalConvertedValue)

        // Images
        ivIcon.clipToOutline = true
        val url = campaign.creatives?.thumbnail ?: campaign.creatives?.cover
        MCOfferwallSDK.LoadImage(url, ivIcon)
        // Replace with your currency icon URL
        MCOfferwallSDK.LoadImage("https://my-cdn/my-coin-image.png", ivCurrency)

        // Promo badge
        if (campaign.promoRatio > 1.0) {
            tvPromo.visibility = View.VISIBLE
            tvPromo.text = "x" + campaign.promoRatio + " Rewards";
        } else {
            tvPromo.visibility = View.GONE
        }

        // In Progress badge
        if (campaign.progress != null
            && campaign.progress.status != null
            && campaign.progress.status != MCCampaignStatus.COMPLETED
            && campaign.progress.status != MCCampaignStatus.CLOSED) {
            tvProgress.visibility = View.VISIBLE
        } else {
            tvProgress.visibility = View.INVISIBLE
        }
    }
})

adView.load()
```

{% endtab %}

{% tab title="Java" %}

```java
MCNativeAdView adView = findViewById(R.id.adView);

adView.setRenderer(new MCNativeAdRenderer() {
    @Override
    public int getItemLayoutId() {
        return R.layout.item_campaign_circular;
    }

    @Override
    public void onBindCampaign(View itemView, MCCampaign campaign, int position) {
        TextView tvName = itemView.findViewById(R.id.mc_tvName);
        TextView tvReward = itemView.findViewById(R.id.mc_tvReward);
        ImageView ivIcon = itemView.findViewById(R.id.mc_ivIcon);
        ImageView ivCurrency = itemView.findViewById(R.id.mc_ivCurrency);
        TextView tvPromo = itemView.findViewById(R.id.mc_tvPromo);
        TextView tvProgress = itemView.findViewById(R.id.mc_tvProgress);

        tvName.setText(campaign.name);

        // Locale-formatted reward
        try {
            NumberFormat nf = NumberFormat.getNumberInstance();
            nf.setMaximumFractionDigits(0);
            tvReward.setText(nf.format(campaign.totalConvertedValue));
        } catch (Exception e) {
            tvReward.setText(String.valueOf((int) campaign.totalConvertedValue));
        }

        // Images
        ivIcon.setClipToOutline(true);
        String url = campaign.creatives != null ? campaign.creatives.thumbnail : null;
        if (url == null || url.isEmpty()) {
            url = campaign.creatives != null ? campaign.creatives.cover : null;
        }
        MCOfferwallSDK.LoadImage(url, ivIcon);
        // Replace with your currency icon URL
        MCOfferwallSDK.LoadImage("https://my-cdn/my-coin-image.png", ivCurrency);

        // Promo badge
        if (campaign.promoRatio > 1.0) {
            tvPromo.setVisibility(View.VISIBLE);
            tvPromo.setText("x" + campaign.promoRatio + " Rewards");
        } else {
            tvPromo.setVisibility(View.GONE);
        }

        // In Progress badge
        if (campaign.progress != null
                && campaign.progress.status != null
                && !MCCampaignStatus.COMPLETED.equals(campaign.progress.status)
                && !MCCampaignStatus.CLOSED.equals(campaign.progress.status)) {
            tvProgress.setVisibility(View.VISIBLE);
        } else {
            tvProgress.setVisibility(View.INVISIBLE);
        }
    }
});

adView.load();
```

{% endtab %}
{% endtabs %}

***

### Example B: Vertical Layout (small cards)

This example renders a vertical list of compact row cards. Each item shows a rounded thumbnail on the left, the title and an "In Progress" badge in the center, and the reward with currency icon on the right. The promo badge floats above the top-right corner of the card.

<figure><img src="/files/kwSjXiUJMbrRTbtISKpw" alt=""><figcaption></figcaption></figure>

#### Step B.1: create the item layout

<pre class="language-xml"><code class="lang-xml">&#x3C;!-- res/layout/item_campaign_small.xml -->
<strong>&#x3C;?xml version="1.0" encoding="utf-8"?>
</strong>&#x3C;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="12dp"
    android:layout_marginBottom="8dp"
    android:clipChildren="false"
    android:clipToPadding="false">

    &#x3C;!-- Card background -->
    &#x3C;LinearLayout
        android:layout_width="match_parent"
        android:layout_height="92dp"
        android:background="@drawable/bg_card"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:paddingStart="16dp"
        android:paddingEnd="16dp">

        &#x3C;!-- Thumbnail (60x60, rounded) -->
        &#x3C;ImageView
            android:id="@+id/mc_ivIcon"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:background="@drawable/mc_bg_thumbnail"
            android:clipToOutline="true"
            android:scaleType="centerCrop"
            android:contentDescription="Campaign icon" />

        &#x3C;!-- Center: title + in-progress badge -->
        &#x3C;LinearLayout
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginStart="12dp"
            android:layout_marginEnd="12dp"
            android:gravity="center_vertical"
            android:orientation="vertical">

            &#x3C;TextView
                android:id="@+id/mc_tvName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:ellipsize="end"
                android:maxLines="1"
                android:fontFamily="sans-serif-medium"
                android:textColor="#FF212121"
                android:textSize="14sp" />

            &#x3C;TextView
                android:id="@+id/mc_tvProgress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:background="@drawable/mc_bg_badge_progress"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:paddingTop="2dp"
                android:paddingBottom="2dp"
                android:text="In Progress"
                android:textColor="#FF424B5A"
                android:textSize="12sp"
                android:visibility="gone" />
        &#x3C;/LinearLayout>

        &#x3C;!-- Right: currency icon + reward -->
        &#x3C;LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:orientation="horizontal">

            &#x3C;ImageView
                android:id="@+id/mc_ivCurrency"
                android:layout_width="16dp"
                android:layout_height="16dp"
                android:scaleType="centerCrop"
                android:contentDescription="Currency" />

            &#x3C;TextView
                android:id="@+id/mc_tvReward"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="3dp"
                android:textColor="#FF212121"
                android:textSize="16sp"
                android:textStyle="bold" />
        &#x3C;/LinearLayout>
    &#x3C;/LinearLayout>

    &#x3C;!-- Promo badge (top-right, overlapping top edge) -->
    &#x3C;TextView
        android:id="@+id/mc_tvPromo"
        android:layout_width="wrap_content"
        android:layout_height="25dp"
        android:layout_gravity="top|end"
        android:layout_marginEnd="12dp"
        android:layout_marginTop="-12dp"
        android:background="@drawable/mc_bg_badge_promo"
        android:gravity="center"
        android:paddingStart="10dp"
        android:paddingEnd="10dp"
        android:textColor="#FFFFFFFF"
        android:textSize="12sp"
        android:textStyle="bold"
        android:visibility="gone" />

&#x3C;/FrameLayout>
</code></pre>

> The `mc_bg_badge_progress` and `mc_bg_badge_promo` drawables are bundled with the SDK. `bg_card` and `mc_bg_thumbnail` are examples of your own drawables.

#### Step B.2: wire the renderer

Set the orientation to vertical and provide an `MCNativeAdRenderer` that inflates the layout above and binds each `MCCampaign` to its views. Also in this case, remember to replace `https://your.cdn/currency.png` with your actual currency icon.

```java
private void setupVerticalSmallView() {
    MCNativeAdView adView = findViewById(R.id.adViewVerticalSmall);

    adView.setOrientation(RecyclerView.VERTICAL);

    adView.setRenderer(new MCNativeAdRenderer() {
        @Override
        public int getItemLayoutId() {
            return R.layout.item_campaign_small;
        }

        @Override
        public void onBindCampaign(View itemView, MCCampaign campaign, int position) {
            TextView tvName = itemView.findViewById(R.id.mc_tvName);
            TextView tvReward = itemView.findViewById(R.id.mc_tvReward);
            TextView tvPromo = itemView.findViewById(R.id.mc_tvPromo);
            TextView tvProgress = itemView.findViewById(R.id.mc_tvProgress);
            ImageView ivIcon = itemView.findViewById(R.id.mc_ivIcon);
            ImageView ivCurrency = itemView.findViewById(R.id.mc_ivCurrency);

            // Title
            tvName.setText(campaign.name);

            // Reward
            try {
                java.text.NumberFormat nf = java.text.NumberFormat.getNumberInstance(Locale.getDefault());
                nf.setMaximumFractionDigits(0);
                tvReward.setText(nf.format(campaign.totalConvertedValue));
            } catch (Exception e) {
                tvReward.setText(String.format(Locale.US, "%.0f", campaign.totalConvertedValue));
            }

            // Promo badge
            if (campaign.promoRatio > 1.0) {
                tvPromo.setVisibility(View.VISIBLE);
                tvPromo.setText(MCDefaultAdRenderer.formatPromo(campaign.promoRatio));
            } else {
                tvPromo.setVisibility(View.GONE);
            }

            // In Progress badge (hide when completed/closed)
            if (campaign.progress != null
                    && campaign.progress.status != null
                    && !MCCampaignStatus.COMPLETED.equals(campaign.progress.status)
                    && !MCCampaignStatus.CLOSED.equals(campaign.progress.status)) {
                tvProgress.setVisibility(View.VISIBLE);
            } else {
                tvProgress.setVisibility(View.GONE);
            }

            // Thumbnail (fallback to cover if missing)
            String url = campaign.creatives != null ? campaign.creatives.thumbnail : null;
            if (url == null || url.isEmpty()) {
                url = campaign.creatives != null ? campaign.creatives.cover : null;
            }
            MCOfferwallSDK.LoadImage(url, ivIcon);

            // Currency icon
            MCOfferwallSDK.LoadImage("https://your.cdn/currency.png", ivCurrency);
        }
    });

    adView.load();
}
```

***

### Example C: Vertical Layout (big cards)

This example creates a vertical list of large cover cards. Each item shows a wide cover image at the top, and a full-width green reward pill at the bottom.

<figure><img src="/files/TwQkxC2t58WnH3O8FZ1t" alt=""><figcaption></figcaption></figure>

#### Step C.1: set the item layout&#x20;

<pre class="language-xml"><code class="lang-xml">&#x3C;!-- res/layout/item_campaign_big.xml -->
<strong>&#x3C;?xml version="1.0" encoding="utf-8"?>
</strong>&#x3C;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="343dp"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:clipChildren="false"
    android:clipToPadding="false">

    &#x3C;LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/bg_cover_card"
        android:orientation="vertical">

        &#x3C;!-- Cover image + promo badge overlay -->
        &#x3C;FrameLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clipChildren="false"
            android:clipToPadding="false">

            &#x3C;ImageView
                android:id="@+id/mc_ivCover"
                android:layout_width="match_parent"
                android:layout_height="172dp"
                android:background="@drawable/bg_cover_top"
                android:clipToOutline="true"
                android:scaleType="centerCrop"
                android:contentDescription="Campaign cover" />

            &#x3C;TextView
                android:id="@+id/mc_tvPromo"
                android:layout_width="wrap_content"
                android:layout_height="25dp"
                android:layout_gravity="bottom|end"
                android:layout_marginEnd="12dp"
                android:layout_marginBottom="12dp"
                android:background="@drawable/bg_promo_badge_bordered"
                android:gravity="center"
                android:paddingStart="10dp"
                android:paddingEnd="10dp"
                android:textColor="#FFFFFFFF"
                android:textSize="12sp"
                android:textStyle="bold"
                android:visibility="gone" />
        &#x3C;/FrameLayout>

        &#x3C;!-- Title -->
        &#x3C;TextView
            android:id="@+id/mc_tvName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginStart="12dp"
            android:layout_marginEnd="12dp"
            android:ellipsize="end"
            android:maxLines="1"
            android:textColor="#FF212121"
            android:textSize="16sp"
            android:textStyle="bold" />

        &#x3C;!-- In Progress badge -->
        &#x3C;TextView
            android:id="@+id/mc_tvProgress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="12dp"
            android:layout_marginEnd="12dp"
            android:layout_marginTop="2dp"
            android:background="@drawable/mc_bg_badge_progress"
            android:paddingStart="8dp"
            android:paddingEnd="8dp"
            android:paddingTop="2dp"
            android:paddingBottom="2dp"
            android:text="In Progress"
            android:textColor="#FF424B5A"
            android:textSize="12sp"
            android:visibility="gone" />

        &#x3C;!-- Green reward pill -->
        &#x3C;LinearLayout
            android:layout_width="match_parent"
            android:layout_height="47dp"
            android:layout_marginTop="10dp"
            android:layout_marginStart="12dp"
            android:layout_marginEnd="12dp"
            android:layout_marginBottom="12dp"
            android:background="@drawable/bg_reward_pill"
            android:gravity="center"
            android:orientation="horizontal">

            &#x3C;ImageView
                android:id="@+id/mc_ivCurrency"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:scaleType="centerCrop"
                android:contentDescription="Currency" />

            &#x3C;TextView
                android:id="@+id/mc_tvReward"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="4dp"
                android:textColor="#FFFFFFFF"
                android:textSize="18sp"
                android:textStyle="bold" />
        &#x3C;/LinearLayout>
    &#x3C;/LinearLayout>

&#x3C;/FrameLayout>
</code></pre>

> `bg_cover_card`, `bg_cover_top`, `bg_reward_pill` and `bg_promo_badge_bordered` are examples of your own drawables. `mc_bg_badge_progress` is bundled with the SDK.

#### Step C.2: wire the renderer

Remember to replace `https://your.cdn/currency.png` with your actual currency icon when copying this code.

```java
private void setupVerticalBigView() {
    MCNativeAdView adView = findViewById(R.id.adViewVerticalBig);

    adView.setOrientation(RecyclerView.VERTICAL);

    adView.setRenderer(new MCNativeAdRenderer() {
        @Override
        public int getItemLayoutId() {
            return R.layout.item_campaign_big;
        }

        @Override
        public void onBindCampaign(View itemView, MCCampaign campaign, int position) {
            ImageView ivCover = itemView.findViewById(R.id.mc_ivCover);
            TextView tvName = itemView.findViewById(R.id.mc_tvName);
            TextView tvProgress = itemView.findViewById(R.id.mc_tvProgress);
            TextView tvReward = itemView.findViewById(R.id.mc_tvReward);
            ImageView ivCurrency = itemView.findViewById(R.id.mc_ivCurrency);
            TextView tvPromo = itemView.findViewById(R.id.mc_tvPromo);

            // Cover image (fallback to thumbnail if missing)
            String coverUrl = campaign.creatives != null ? campaign.creatives.cover : null;
            if (coverUrl == null || coverUrl.isEmpty()) {
                coverUrl = campaign.creatives != null ? campaign.creatives.thumbnail : null;
            }
            MCOfferwallSDK.LoadImage(coverUrl, ivCover);

            // Title
            tvName.setText(campaign.name);

            // In Progress badge (hide when completed/closed)
            boolean hasProgress = campaign.progress != null
                    && campaign.progress.status != null
                    && !MCCampaignStatus.COMPLETED.equals(campaign.progress.status)
                    && !MCCampaignStatus.CLOSED.equals(campaign.progress.status);
            tvProgress.setVisibility(hasProgress ? View.VISIBLE : View.GONE);

            // Reward
            try {
                java.text.NumberFormat nf = java.text.NumberFormat.getNumberInstance(Locale.getDefault());
                nf.setMaximumFractionDigits(0);
                tvReward.setText(nf.format(campaign.totalConvertedValue));
            } catch (Exception e) {
                tvReward.setText(String.format(Locale.US, "%.0f", campaign.totalConvertedValue));
            }

            // Currency icon
            MCOfferwallSDK.LoadImage("https://your.cdn/currency.png", ivCurrency);

            // Promo badge
            if (campaign.promoRatio > 1.0) {
                tvPromo.setVisibility(View.VISIBLE);
                tvPromo.setText(MCDefaultAdRenderer.formatPromo(campaign.promoRatio));
            } else {
                tvPromo.setVisibility(View.GONE);
            }
        }
    });

    adView.load();
}
```

***

### Key Points for Custom Layouts

#### Click Handling

The SDK wires click handlers **automatically** — tapping any item opens the campaign's detail page. You can override this with `setOnCampaignClickListener()`.

#### Image Loading

Use the SDK's built-in image loader in your renderer:

```java
MCOfferwallSDK.LoadImage(url, imageView);
```

It handles background downloading, bitmap decoding, and RecyclerView recycling. No external library needed.

#### RecyclerView Recycling Tip

For horizonatl layouts, set `android:minHeight` on the root layout to ensure consistent item heights.

***

### Next Steps

* Need the full data reference? See [Data Reference →](/android/sdk-native/sdk-native-data-reference)
* Need simpler tweaks? See [Simple Customization →](/android/sdk-native/sdk-native-customizations)


# SDK Native - Data Reference

## Native SDK — Data Reference

Complete reference for all data objects available in the native campaigns SDK.

***

<figure><img src="/files/kFb5dDTVGyAbNRBwd8KI" alt=""><figcaption></figcaption></figure>

### MCCampaign

The main campaign object passed to your renderer's `onBindCampaign`.

<table><thead><tr><th width="238">Field</th><th width="126">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>String</code></td><td>Unique campaign identifier</td></tr><tr><td><code>name</code></td><td><code>String</code></td><td>Campaign display name</td></tr><tr><td><code>type</code></td><td><code>String</code></td><td>Campaign type. See <code>MCCampaignType</code> constants below.</td></tr><tr><td><code>creatives</code></td><td><code>MCCreatives</code></td><td>Image URLs (see below)</td></tr><tr><td><code>links</code></td><td><code>MCLinks</code></td><td>Tracking and navigation URLs (see below)</td></tr><tr><td><code>totalConvertedValue</code></td><td><code>double</code></td><td>Total reward in your virtual currency (including promo)</td></tr><tr><td><code>remainingConvertedValue</code></td><td><code>double</code></td><td>Remaining reward the user can earn</td></tr><tr><td><code>promoRatio</code></td><td><code>double</code></td><td>Promotional multiplier (1.0 = no promo, 2.0 = double)</td></tr><tr><td><code>progress</code></td><td><code>MCProgress</code></td><td>User's progress. <strong>null</strong> if the user hasn't started.</td></tr></tbody></table>

***

### MCCreatives

Image assets for a campaign.

<table><thead><tr><th width="238">Field</th><th width="156">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>thumbnail</code></td><td><code>String</code> (nullable)</td><td>Small square preview image URL</td></tr><tr><td><code>cover</code></td><td><code>String</code> (nullable)</td><td>Full-size banner image URL</td></tr></tbody></table>

***

### MCLinks

Tracking and navigation URLs. Managed automatically by the SDK when using `MCNativeAdView`.

<table><thead><tr><th width="236">Field</th><th width="158">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>trackingUrl</code></td><td><code>String</code> (nullable)</td><td>Click tracking URL</td></tr><tr><td><code>trackingPixelUrl</code></td><td><code>String</code> (nullable)</td><td>Impression pixel URL (fired automatically by the SDK)</td></tr><tr><td><code>detailUrl</code></td><td><code>String</code> (nullable)</td><td>Campaign detail page URL (opened on click)</td></tr></tbody></table>

***

### MCProgress

User's progress on a campaign. **Null** when the user hasn't started the campaign.

<table><thead><tr><th width="230">Field</th><th width="153">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td><code>String</code></td><td>Progress status. See <code>MCCampaignStatus</code> constants below.</td></tr><tr><td><code>eventsCompleted</code></td><td><code>int</code></td><td>Number of events the user has completed</td></tr><tr><td><code>totalEvents</code></td><td><code>int</code></td><td>Total events required to complete the campaign</td></tr><tr><td><code>valueEarned</code></td><td><code>double</code></td><td>Reward earned so far in your virtual currency</td></tr><tr><td><code>progressValue</code></td><td><code>double</code></td><td>Completion percentage (0–100)</td></tr></tbody></table>

***

### MCMeta

Response metadata returned alongside the campaign list.

<table><thead><tr><th width="228">Field</th><th width="164">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>version</code></td><td><code>String</code></td><td>API response format version</td></tr><tr><td><code>count</code></td><td><code>int</code></td><td>Number of campaigns returned</td></tr></tbody></table>

***

### MCCampaignType (constants)

Known campaign type values. Compare against `campaign.type`:

| Constant                       | Value            |
| ------------------------------ | ---------------- |
| `MCCampaignType.MULTI_REWARD`  | `"MultiReward"`  |
| `MCCampaignType.PLAY_TO_EARN`  | `"Play2Earn"`    |
| `MCCampaignType.SINGLE_REWARD` | `"SingleReward"` |

```java
if (MCCampaignType.MULTI_REWARD.equals(campaign.type)) {
    // Handle multi-reward campaign
}
```

> The `type` field is a `String`, not an enum, because new types may be added in the future. Unknown types will not break your app.

***

### MCCampaignStatus (constants)

Known progress status values. Compare against `campaign.progress.status`:

| Constant                     | Value         |
| ---------------------------- | ------------- |
| `MCCampaignStatus.CLICKED`   | `"clicked"`   |
| `MCCampaignStatus.STARTED`   | `"started"`   |
| `MCCampaignStatus.INSTALLED` | `"installed"` |
| `MCCampaignStatus.COMPLETED` | `"completed"` |
| `MCCampaignStatus.EXPIRED`   | `"expired"`   |
| `MCCampaignStatus.CLOSED`    | `"closed"`    |

```java
if (campaign.progress != null
        && MCCampaignStatus.COMPLETED.equals(campaign.progress.status)) {
    // Campaign completed!
}
```

> The `status` field is a `String`, not an enum, because new statuses may be added in the future.

***

### Image Loading

The SDK provides a built-in image loader. Use it anywhere in your renderer:

```java
MCOfferwallSDK.LoadImage(url, imageView);
```

* Runs on a background thread
* Decodes bitmap and sets it on the UI thread
* Handles RecyclerView view recycling correctly
* No external library needed (no Glide, Picasso, or Coil)

> You can also use your own image loading library if you prefer. The SDK does not restrict this.

***

### External References

For a more comprehensive list of examples and mappings, you can also checkout this [Figma Showcase](https://www.figma.com/board/7ctPofi9irp7PV4TwT3a7l/MyChips-SDK-Default-Layouts?node-id=0-1\&t=Ej26WBktLfW0wu3A-1) of different templates.


# Reward User

#### 1. Fully Managed by MyChips

{% hint style="info" %}
Use this method only if you have selected Self-Managed Currency. If you already support S2S postback please skip this snippet.
{% endhint %}

**1.1 Implements the Interface in your MainActivity**

{% tabs %}
{% tab title="Java" %}

```java
public class MainActivity implements RewardCallback {
  ...
    @Override
    public void OnRewardReceived(RewardDTO rewardDTO) {
         //rewardDTO.GetRewardInVirtualCurrency()
         //HANDLE HERE YOUR CUSTOM LOGIC
    }

    @Override
    public void onError(Exception e) {

    }
...
}
```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
class MainActivity : RewardCallback {
   ...
     override fun onRewardReceived(rewardDTO: RewardDTO) {
        // Handle reward received logic here
        //rewardDTO.GetRewardInVirtualCurrency()
    }

    override fun onRewardError(e: Exception) {
        // Handle error here
    }
  ...
}
```

{% endtab %}
{% endtabs %}

#### 1.2 Check for new reward at app open and app resume

{% tabs %}
{% tab title="Java" %}

```java
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        MCOfferwallSDK.CheckReward("your adunit here",this);
        ...
}

@Override
    protected void onResume() {
        super.onResume();
        MCOfferwallSDK.CheckReward("your adunit here",this);
}

```

{% endtab %}

{% tab title="Kotlin" %}

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    MCOfferwallSDK.CheckReward(adUnitId, this)
}

override fun onResume() {
    super.onResume()
    MCOfferwallSDK.CheckReward(adUnitId, this)
}
```

{% endtab %}
{% endtabs %}

#### 2. Server-to-Server (S2S) Postbacks

If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# React Native

Follow the steps to integrate MyChips on your React Native app.

<details>

<summary>Release Note</summary>

### **Version:** 0.8.7 (current)

Date: 2025-11-20

New Feature&#x20;

Allows the host app to actively trigger the Offerwall’s back navigation logic.

</details>

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/9ClQ9eAOT3DJYpimhNdK" %}
[Install SDK](/react-native/install-sdk)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/hkxqoexmEPaMjQ2I5kzB" %}
[Reward User](/react-native/reward-user)
{% endcontent-ref %}


# Install SDK

## **1. Introduction** <a href="#h-1-introduction" id="h-1-introduction"></a>

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your application, enabling the display of an engaging offerwall.

## 2.Prerequisites <a href="#h-3-sdk-integration" id="h-3-sdk-integration"></a>

* Ensure a React native project is already set up correctly.
* &#x20;The official documentation for Set Up Your Environment:                  <https://reactnative.dev/docs/set-up-your-environment>
* If you're using a version prior to React Native 0.74,  here are the suggested steps to set up the project:

1. Modify the `boost.podspec` file located in the directory:

```
/node_modules/react-native/third-party-podspecs/boost.podspec
```

2. Ensure that the Boost URL and SHA256 code are valid. If they are not, update the `spec.source` in the `boost.podspec` file.

For example, if you're using version 0.68, the correct `spec.source` is as follows:

```sh
spec.source = { :http => 'https://sourceforge.net/projects/boost/files/boost/1.76.0/boost_1_76_0.tar.bz2',
                   :sha256 => 'f0397ba6e982c4450f27bf37a5623a14636ea583318c41' }
```

## **3. SDK Integration** <a href="#h-3-sdk-integration" id="h-3-sdk-integration"></a>

<https://www.npmjs.com/package/mychips-react-sdk>

**3.1 Adding the SDK**

<pre><code><strong>npm install mychips-react-sdk
</strong></code></pre>

```
yarn add mychips-react-sdk
```

If you're using a version prior to React Native 0.74, you need to modify the `mychips-react-sdk.podspec` file located in the `mychips-react-sdk` library.

1. Navigate to **line 14** and remove the variable `min_ios_version_supported`.
2. Replace it with the minimal iOS version supported, specified as a hardcoded numeric value.

For example, if the minimal iOS version supported is 15, replace the variable with the hardcoded value `15.0`.

```
 s.platforms    = { :ios => 15 }
```

After making the necessary changes, run the command in the ios folder:

```sh
pod install
```

**3.2 Adding the SDK Dependency to App-Level**&#x20;

Be sure to add this dependencies via yarn or npm

```json
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.23.1",
    "@react-native-community/netinfo": "^11.3.2",
    "react-native-webview": "^13.10.3"
  }
```

**3.3 Configuring the Android Manifest**

In your `AndroidManifest.xml`, add the following:

**Add Permission for Internet Access:**

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

## **4. Initializing the SDK**&#x20;

In your main activity’s `onCreate` method, import and initialize the SDK:

{% tabs %}
{% tab title="Typescript" %}

```typescript
import { MCOfferwallSDK,MCOfferwallView} from 'mychips-react-sdk';
export default function App() {
  MCOfferwallSDK.init("your api key");
}
```

{% endtab %}
{% endtabs %}

> Obtain your API key and User ID from[ **Universal Developer Portal**](https://dashboard.maf.ad/)

#### **4.1 (Mandatory)** – Set **Google Advertising ID (Android)** and **Identifier for Advertisers (iOS)**

Improve reward tracking by passing the Google Advertising ID ([Official documentation](https://developer.android.com/training/articles/ad-id)) for Android devices and the IDFA for iOS devices.

**Android:**

```java
MCOfferwallSDK.setGaid("HERE YOUR GAID");
```

replace "HERE YOUR GAID" with your actual Google Advertising ID variable or value.

**iOS:**

```java
MCOfferwallSDK.setIdfa("HERE YOUR IDFA");
```

Replace "HERE YOUR IDFA" with your actual IDFA variable or value.

#### 4.2 (Optional) Set userId if you have your own unique id

```java
  MCOfferwallSDK.setUserId("HERE YOUR USER ID");
```

Replace `"`HERE YOUR USER ID`"` with your actual user ID variable or value.

If you do not provide a specific user ID, one will be automatically generated.

#### 4.3(Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.setAge(30);
```

Replace 30 with your actual user age variable or value (integer).

💡 **Note:**

* The value should be an integer (e.g., 18, 25, 30).
* Expected range is 0–100 (inclusive).

#### 4.4(Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.setGender(MCGenderEnum.MALE);
```

Available enum values:

```java
  MCGenderEnum.MALE
  MCGenderEnum.FEMALE
  MCGenderEnum.OTHER
```

#### 4.5 (Optional) Set Custom Parameters (`aff_sub1`–`aff_sub5` )

We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom values.

```java
MCOfferwallSDK.setAffSub1("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub2("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub3("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub4("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub5("HERE YOUR CUSTOM VALUE");
```

Replace `"`HERE YOUR CUSTOM VALUE`"` with your actual custom value.

## **5. Display the Offerwall**

> Replace ‘AD\_UNIT\_ID’ with your actual Ad unit ID.

Create a new page and navigate to it

```typescript
const ADUNITID="{ADUNIT ID HERE}";

return (
    <View style={styles.container}>
      <View style={styles.webviewContainer}>
        <MCOfferwallView adunitId={ADUNITID}/>
      </View>
    </View>
  );
```

Style

```typescript
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  webviewContainer: {
    flex: 1,
    width: '100%',
  },
});
```

> Your Ad unit ID can be found at [**link**](https://dashboard.maf.ad/)


# Reward User

#### 1. Fully Managed by MyChips

{% hint style="info" %}
Use this method only if you have selected Self-Managed Currency. If you already support S2S postback please skip this snippet.
{% endhint %}

#### 1.1 Check for new reward at app open and app resume

{% tabs %}
{% tab title="Typescript" %}

<pre class="language-typescript"><code class="lang-typescript">
React.useEffect(() => {
<strong>   //handler for App State Changed
</strong><strong>   const handleAppStateChange = (nextAppState: any) => {
</strong>       if (nextAppState === 'active') {
        
        
        const fetchReward = async () =>{
          return await MCOfferwallSDK.GetReward(ADUNITID)
        } 
        //check for new rewards
        fetchReward().then((reward)=>{
          if(reward){
            console.log(reward.getRewardInVirtualCurrency())
          }
         }).catch()

       }
     };
  
     //subscribe the appstate event change
     const subscription = AppState.addEventListener('change', handleAppStateChange);
    
     return () => {
         //umregister event
         subscription.remove();
     };
 });

</code></pre>

{% endtab %}
{% endtabs %}

#### 2. Server-to-Server (S2S) Postbacks

If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# RN Expo

Follow the steps to integrate MyChips on your React Native Expo app.

{% content-ref url="/pages/XpaeiuUAMMzaHGBg2QgZ" %}
[Install SDK](/rn-expo/install-sdk)
{% endcontent-ref %}

{% content-ref url="/pages/OQLNE38TUjFw6mpzZGGW" %}
[Reward User](/rn-expo/reward-user)
{% endcontent-ref %}


# Install SDK

## **1. Introduction** <a href="#h-1-introduction" id="h-1-introduction"></a>

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your application, enabling the display of an engaging offerwall.

## 2.Prerequisites <a href="#h-3-sdk-integration" id="h-3-sdk-integration"></a>

* Ensure a Expo React Native project is already set up correctly.
* **Be cautious:** If you’re using an Expo SDK version lower than v**52**, carefully follow the instructions in **Section 3.2** of the next part to ensure compatibility.

## **3. SDK Integration** <a href="#h-3-sdk-integration" id="h-3-sdk-integration"></a>

<https://www.npmjs.com/package/mychips-react-sdk>

**3.1 Adding the SDK**

<pre><code><strong>npm install mychips-react-sdk
</strong></code></pre>

```
npm install expo-blur@~14.0.3
```

**3.2 Adding the SDK Dependency to App-Level**&#x20;

Be sure to add this dependencies via yarn or npm

```sh
npm install @react-native-async-storage/async-storage@1.23.1
```

```sh
npm install @react-native-community/netinfo react-native-webview
```

* If you’re using an Expo SDK version lower than v52, please follow these additional steps to ensure everything works correctly.
* Make sure all instances of **react-native-webview** in your project use the same version. If the version in the MyChips SDK differs from your project’s version, open the MyChips SDK folder.

```
/node_modules/mychips-react-sdk/package.json
```

* Edit your **package.json** and update **react-native-webview** to match your project’s version.\
  For instance, if you’re using **Expo 0.51**, the default **react-native-webview** version is **13.8.6**.\
  Modify your **package.json** like this:

```json
{
  ...
  "dependencies": {
    ...
    "react-native-webview": "13.8.6",
    ...
  }
}

```

* Save **package.json**, then delete **package-lock.json** and the **node\_modules** folder.

## **4. Initializing the SDK**&#x20;

In your main activity’s `onCreate` method, import and initialize the SDK:

{% tabs %}
{% tab title="Typescript" %}

```typescript
import { MCOfferwallSDK,MCOfferwallView} from 'mychips-react-sdk';
export default function App() {
  MCOfferwallSDK.init("your api key");
}
```

{% endtab %}
{% endtabs %}

> Obtain your API key and User ID from[ **Universal Developer Portal**](https://dashboard.maf.ad/)

#### **4.1 (Mandatory)** – Set **Google Advertising ID (Android)** and **Identifier for Advertisers (iOS)**

Improve reward tracking and eCPM performance by passing the Google Advertising ID ([Official documentation](https://developer.android.com/training/articles/ad-id)) for Android devices and the IDFA for iOS devices.

**Android:**

```java
MCOfferwallSDK.setGaid("HERE YOUR GAID");
```

replace "HERE YOUR GAID" with your actual Google Advertising ID variable or value.

**iOS:**

```java
MCOfferwallSDK.setIdfa("HERE YOUR IDFA");
```

Replace "HERE YOUR IDFA" with your actual IDFA variable or value.

#### 4.2 (Optional) Set userId if you have your own unique id

```java
  MCOfferwallSDK.setUserId("HERE YOUR USER ID");
```

Replace `"`HERE YOUR USER ID`"` with your actual user ID variable or value.

If you do not provide a specific user ID, one will be automatically generated.

#### 4.3(Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.setAge(30);
```

Replace 30 with your actual user age variable or value (integer).

💡 **Note:**

* The value should be an integer (e.g., 18, 25, 30).
* Expected range is 0–100 (inclusive).

#### 4.4(Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```java
  MCOfferwallSDK.setGender(MCGenderEnum.MALE);
```

Available enum values:

```java
  MCGenderEnum.MALE
  MCGenderEnum.FEMALE
  MCGenderEnum.OTHER
```

#### 4.5 (Optional) Set Custom Parameters (`aff_sub1`–`aff_sub5` )

We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom valu

```java
MCOfferwallSDK.setAffSub1("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub2("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub3("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub4("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub5("HERE YOUR CUSTOM VALUE");
```

Replace `"`HERE YOUR CUSTOM VALUE`"` with your actual custom value.

## **5. Display the Offerwall**

> Replace ‘AD\_UNIT\_ID’ with your actual Ad unit ID.

Create a new page and navigate to it

```typescript
const ADUNITID="{ADUNIT ID HERE}";

return (
    <View style={styles.container}>
      <View style={styles.webviewContainer}>
        <MCOfferwallView adunitId={ADUNITID}/>
      </View>
    </View>
  );
```

Style

```typescript
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  webviewContainer: {
    flex: 1,
    width: '100%',
  },
});
```

{% content-ref url="/pages/OQLNE38TUjFw6mpzZGGW" %}
[Reward User](/rn-expo/reward-user)
{% endcontent-ref %}


# Reward User

#### 1. Fully Managed by MyChips

{% hint style="info" %}
Use this method only if you have selected Self-Managed Currency. If you already support S2S postback please skip this snippet.
{% endhint %}

#### 1.1 Check for new reward at app open and app resume

{% tabs %}
{% tab title="Typescript" %}

<pre class="language-typescript"><code class="lang-typescript">
React.useEffect(() => {
<strong>   //handler for App State Changed
</strong><strong>   const handleAppStateChange = (nextAppState: any) => {
</strong>       if (nextAppState === 'active') {
        
        
        const fetchReward = async () =>{
          return await MCOfferwallSDK.GetReward(ADUNITID)
        } 
        //check for new rewards
        fetchReward().then((reward)=>{
          if(reward){
            console.log(reward.getRewardInVirtualCurrency())
          }
         }).catch()

       }
     };
  
     //subscribe the appstate event change
     const subscription = AppState.addEventListener('change', handleAppStateChange);
    
     return () => {
         //umregister event
         subscription.remove();
     };
 });

</code></pre>

{% endtab %}
{% endtabs %}

#### 2. Server-to-Server (S2S) Postbacks

If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# iOS

<details>

<summary>Release Note</summary>

### **Version:** 1.1.0 (current)

Date: 2026-04-23

**New Feature**&#x20;

* SDK - Native

Added new customization options for the Android Native SDK, including configurable scrolling, in-app campaign details, custom icons, text colors, loading views, click handling, and loading lifecycle callbacks.&#x20;

</details>

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/40LM8PkD2XVNni4wQ9zf" %}
[Install SDK](/ios/install-sdk)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/bf18wublEt81HmrwilrS" %}
[Reward User](/ios/reward-user)
{% endcontent-ref %}


# Install SDK

## 1. Introduction

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your iOS application, enabling the display of an engaging offerwall.

Once installed, you can choose between two integration options:

* **Offerwall** — A full-screen WebView experience managed by the SDK. See [Offerwall Integration →](/ios/sdk-offerwall)
* **Native Campaigns** — A customizable campaign list that you embed directly in your app's UI. See [Native Integration →](/ios/sdk-native)

Both options use the same SDK. Install it once, then pick the integration that fits your app.

## 2. Prerequisites

* Xcode 16 or later
* Minimum deployment target **iOS 14**.

## 3. SDK Integration

In your project in Xcode go to **File -> Add Package Dependencies...** and then add the dependency using the following repo: <https://github.com/myappfree/mychips-ios-sdk>

Now in your project you can import the SDK:

```swift
import MyChipsSdk
```

## 4. Initializating the SDK

Before using the sdk or showing the offerwall you must initialize it:

```swift
//replace "YOUR_API_KEY"
MCOfferwallSDK.shared.configure(apiKey: "YOUR_API_KEY")
```

> Obtain your API key  from [Universal Developer Portal](https://dashboard.maf.ad)

#### 4.1 (Mandatory) Set Identifier for Advertisers

Improve reward tracking and eCPM performance by passing the IDFA.&#x20;

```swift
MCOfferwallSDK.shared.setIdfa(idfa: "YOUR_IDFA")
```

#### 4.2 (Optional) Set userId if you have your own unique id

```swift
MCOfferwallSDK.shared.setUserId(userId: "YOUR_USER_ID")
```

> Replace `"`YOUR\_USER\_ID`"` with your actual user ID variable or value.
>
> If you do not provide a specific user ID, one will be automatically generated.

#### 4.3 (Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```swift
  MCOfferwallSDK.shared.setAge(30)
```

Replace 30 with your actual user age variable or value (integer).

💡 **Note:**

* The value should be an integer (e.g., 18, 25, 30).
* Expected range is 0–100 (inclusive).

#### 4.4 (Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```swift
  MCOfferwallSDK.shared.setGender(.female)
```

Available enum values:

```swift
 .male
 .female
 .other
```

#### 4.5  (Optional) Set custom parameters (`aff_sub1`–`aff_sub5` )

```swift
MCOfferwallSDK.shared.setAffSub1("YOUR_CUSTOM_VALUE")
MCOfferwallSDK.shared.setAffSub2("YOUR_CUSTOM_VALUE")
MCOfferwallSDK.shared.setAffSub3("YOUR_CUSTOM_VALUE")
MCOfferwallSDK.shared.setAffSub4("YOUR_CUSTOM_VALUE")
MCOfferwallSDK.shared.setAffSub5("YOUR_CUSTOM_VALUE")
```

> We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom values.
>
> Replace `"`YOUR\_CUSTOM\_VALUE`"` with your actual custom value.

### **5. Next Steps**

Choose your integration:

<table><thead><tr><th width="174">Integration</th><th width="317">Description</th><th>Guide</th></tr></thead><tbody><tr><td><strong>Offerwall</strong></td><td>Full-screen WebView experience</td><td><a href="/pages/5fu0t3aeysUOsDFBIvQI">Offerwall Integration →</a></td></tr><tr><td><strong>Native Campaigns</strong></td><td>Customizable campaign list in your UI</td><td><a href="/pages/Dh0pHdE9elZzizZIOsGc">Native Quick Start →</a></td></tr></tbody></table>


# SDK - Offerwall

## Offerwall Integration

> **Prerequisite:** Complete the [SDK Installation](/ios/install-sdk) first.

The Offerwall displays a full-screen WebView with all available campaigns. The SDK manages the entire UI — you just present it.

### 1. Display the Offerwall

The SDK ships `MCWebViewController`, a `UIViewController` subclass you can push onto a `UINavigationController` (UIKit) or wrap with `UIViewControllerRepresentable` (SwiftUI).

{% tabs %}
{% tab title="SwiftUI" %}
First, wrap `MCWebViewController` so SwiftUI can host it:

```swift
import SwiftUI
import MyChipsSdk

// Definition of the WebViewWrapper struct.
// UIViewControllerRepresentable allows integrating a UIViewController into a SwiftUI interface.
struct WebViewWrapper: UIViewControllerRepresentable {

    // Property for the ad unit identifier, passed during initialization.
    let adunitId: String
    
    // Access to the SwiftUI environment to manage presentation mode.
    // @Environment provides access to shared values throughout the app.
    @Environment(\.presentationMode) var presentationMode

   // Creates and configures the UIViewController (required by UIViewControllerRepresentable).
    func makeUIViewController(context: Context) -> UINavigationController {
        // Initialize the custom MCWebViewController with the adunitId.
        let webVC = MCWebViewController(
            adunitId: adunitId,
            // Closure defining the behavior when the webViewController is closed.
            // This dismisses the view controller using presentationMode.
            onClose: {
                presentationMode.wrappedValue.dismiss()
            }
        )
        
        // Wrap the webViewController inside a UINavigationController.
        // This allows navigation functionality if required.
        return UINavigationController(rootViewController: webVC)
    }

    // Method to update the existing UIViewController with new data (required by UIViewControllerRepresentable).
    // Not implemented here as no updates are needed for this functionality.
    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}
```

Then use it inside a `NavigationStack` / `NavigationView`:

```swift
// Definition of the WebViewPage struct, which conforms to the View protocol.
// This struct represents a SwiftUI view that embeds the WebViewWrapper.
struct ContentView: View {
    // The body property defines the content and layout of the view.
    var body: some View {
        NavigationStack {
            // Adding a NavigationLink that navigates to WebView when tapped.
            NavigationLink("Open Offerwall") {
                // Embedding the WebViewWrapper inside the view.
                // The WebViewWrapper takes an adunitId as a parameter.
                WebViewWrapper(adunitId: "AD_UNIT_ID")
                    // Hides the back button in the navigation bar for this view.
                    .navigationBarBackButtonHidden()
            }
        }
    }
}
```

{% endtab %}

{% tab title="UIKit" %}

```swift
import UIKit
import MyChipsSdk

class MainViewController: UIViewController {
    @objc func showOfferwall() {
        let webVC = MCWebViewController(adunitId: "AD_UNIT_ID") { [weak self] in
            self?.navigationController?.popViewController(animated: true)
        }
        navigationController?.pushViewController(webVC, animated: true)
    }
}
```

**That's it.** If you want to open the offerwall on a button press, you can just add a button and invoke the `showOfferwall` method:

```swift
override func loadView() {
    // Create a new UIView instance
    let view = UIView()
    view.backgroundColor = .white // Set background color
    
    // Create and configure the button
    let nextButton = UIButton(type: .system)
    nextButton.setTitle("Show Offerwall", for: .normal)
    nextButton.addTarget(self, action: #selector(showOfferwall), for: .touchUpInside)
    nextButton.translatesAutoresizingMaskIntoConstraints = false // Disable default autoresizing mask
    
    // Add the button to the view
    view.addSubview(nextButton)
    
    // Add constraints for button
    NSLayoutConstraint.activate([
        nextButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        nextButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    ])
    
    // Set the view of the view controller
    self.view = view
}
```

{% endtab %}
{% endtabs %}

> Replace `AD_UNIT_ID` with your Ad unit ID from the [Developer Portal](https://dashboard.maf.ad/).

### **2. (Optional) Customize Toolbar Title**

```swift
MCOfferwallSDK.shared.setToolbarTitle("My Rewards")
```

If no title is set, the toolbar remains blank.


# SDK - Native

### Quick Start

> **Prerequisite:** Complete the [SDK Installation](/ios/install-sdk) first. Minimum iOS deployment target: 14. Minimum SDK version required: 1.2.1.

Native Campaigns lets you display a scrollable list of campaigns directly in your app's UI. The SDK provides a default layout — you just drop it in and call `load()`.

<figure><img src="/files/M3ncHfDPiMZSsoQfmi9T" alt="" width="341"><figcaption></figcaption></figure>

***

### **1. Set the Ad Unit ID**

After `configure`, set the ad unit ID for native campaigns:

```swift
MCOfferwallSDK.shared.configure(apiKey: "YOUR_API_KEY")
MCOfferwallSDK.shared.setUserId(userId: "YOUR_USER_ID")
MCOfferwallSDK.shared.setAdunitId("YOUR_AD_UNIT_ID")
```

> Get your Ad unit ID from the [Developer Portal](https://dashboard.maf.ad/).

### **2. Add the View to Your Screen**

`MCNativeAdView` is a plain `UIView` — add it anywhere you can add a subview.

{% tabs %}
{% tab title="SwiftUI" %}
Wrap `MCNativeAdView` with `UIViewRepresentable`:

```swift
import SwiftUI
import MyChipsSdk

struct MCNativeAdViewRepresentable: UIViewRepresentable {
    func makeUIView(context: Context) -> MCNativeAdView {
        let adView = MCNativeAdView()
        adView.load()
        return adView
    }

    func updateUIView(_ uiView: MCNativeAdView, context: Context) {}
}
```

Then use it in your view:

```swift
struct CampaignsView: View {
    var body: some View {
        MCNativeAdViewRepresentable()
            .frame(height: 260)
    }
}
```

{% endtab %}

{% tab title="UIKit" %}

```swift
import UIKit
import MyChipsSdk

class CampaignsViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let adView = MCNativeAdView()
        adView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(adView)

        NSLayoutConstraint.activate([
            adView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16),
            adView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            adView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            adView.heightAnchor.constraint(equalToConstant: 260)
        ])

        adView.load()
    }
}
```

{% endtab %}
{% endtabs %}

**That's it.** The SDK handles everything:

* Fetches campaigns from the API
* Shows a loading skeleton while fetching
* Displays campaigns in a horizontal scrollable list
* Tracks impressions automatically
* Opens the campaign detail page on click

***

### 3. (Optional) Open Campaign Details In-App

By default, tapping a campaign opens the detail page in Safari. To keep users inside your app using an in-app WebView:

```swift
MCOfferwallSDK.shared.setOpenInApp(true)
```

When `openInApp` is `true`, the SDK presents an `MCWebViewController` from the nearest `UIViewController` in the responder chain. You can also set a title for the in-app WebView:

```swift
MCOfferwallSDK.shared.setToolbarTitle("My Rewards")
```

> **Tip:** For tighter control over which view controller is used to present the WebView, set `adView.presentingController = self` on your containing `UIViewController`.

### 4. What Happens Behind the Scenes

When you call `adView.load()`, the SDK:

1. Shows a **pulsing skeleton placeholder** that matches the layout direction
2. Calls the API to fetch campaigns
3. Replaces the skeleton with the **campaign list**
4. **Fires impression pixels** automatically when each campaign becomes visible
5. **Opens the campaign detail page** (in Safari or an in-app WebView) when the user taps a campaign

No manual tracking or click handling is needed.

***

### **Next Steps**

* Want to customize the look and feel with small effort? See [Customizations →](/ios/sdk-native/sdk-native-customizations)
* Need a completely different card design? See [Custom Layouts →](/ios/sdk-native/sdk-native-custom-layout)
* Need the full data reference? See [Data Reference →](/ios/sdk-native/sdk-native-data-reference)


# SDK Native - Customizations

## Native SDK — Customizations

> **Prerequisite:** Complete the [Native SDK - Quick Start](/ios/sdk-native) first.

These customizations require **no custom cell class** — you configure the built-in default renderer or extend it with small tweaks.

***

### 1. Configuration Options

#### 1.1 Scroll Direction

```swift
// Horizontal (default)
adView.setOrientation(.horizontal)

// Vertical list
adView.setOrientation(.vertical)
```

<figure><img src="/files/U7cfOCrayZHgA7SwNMss" alt="" width="308"><figcaption></figcaption></figure>

#### 1.2 Maximum Number of Campaigns

```swift
adView.setMaxCampaigns(5)   // Show at most 5 campaigns
adView.setMaxCampaigns(0)   // Show all (default)
```

#### 1.3 Open Campaign Details In-App

By default, tapping a campaign opens in Safari. To keep users inside your app:

```swift
MCOfferwallSDK.shared.setOpenInApp(true)
```

When `openInApp` is `true`, the SDK presents `MCWebViewController` from the nearest `UIViewController` in the responder chain. For tighter control, you can pin the presenting view controller explicitly:

```swift
adView.presentingController = self   // 'self' is your UIViewController
```

***

### 2. Change the Currency Icon

The default renderer shows a coin icon next to the reward value.

<figure><img src="/files/cS0ENLiiqrcaAWMqdILq" alt="" width="133"><figcaption></figcaption></figure>

You can replace it in two ways:

**Option A: Set a custom URL**

```swift
let renderer = MCDefaultAdRenderer()
renderer.setCurrencyIconUrl("https://example.com/my_coin.png")
adView.setRenderer(renderer)
adView.load()
```

**Option B: Use a local asset**

To use an image from your app's asset catalog, disable the URL-based icon and set the image in `configure(cell:campaign:at:)`:

```swift
import UIKit
import MyChipsSdk

final class GemRenderer: MCDefaultAdRenderer {
    override init() {
        super.init()
        // Disable the default CDN icon — we'll set our own below.
        setCurrencyIconUrl("")
    }

    override func configure(cell: UICollectionViewCell, campaign: MCCampaign, at index: Int) {
        super.configure(cell: cell, campaign: campaign, at: index)
        if let cell = cell as? MCDefaultAdCell {
            cell.currencyImageView.image = UIImage(named: "ic_gem")
        }
    }
}

adView.setRenderer(GemRenderer())
adView.load()
```

> **Important:** Call `setCurrencyIconUrl("")` when using a local asset. This prevents the default CDN icon from loading asynchronously and overwriting your image.

***

### 3. Customize Text Colors

By default the name and reward labels use `UIColor.label`, so they adapt automatically to the system light/dark theme. If you want to brand them, subclass `MCDefaultAdRenderer` and override the colors after the default binding:

```swift
final class BrandedRenderer: MCDefaultAdRenderer {
    override func configure(cell: UICollectionViewCell, campaign: MCCampaign, at index: Int) {
        super.configure(cell: cell, campaign: campaign, at: index)
        if let cell = cell as? MCDefaultAdCell {
            cell.rewardLabel.textColor = UIColor(red: 0x19/255, green: 0x76/255, blue: 0xD2/255, alpha: 1) // Blue
        }
    }
}

adView.setRenderer(BrandedRenderer())
```

> **Dark mode tip:** when you pick a custom text color, prefer a `UIColor(dynamicProvider:)` — or a pair of asset-catalog colors — so your brand remains readable in both appearances. The card sits directly on the host's background, so hardcoded dark colors will disappear in dark mode.

#### Default Cell Subviews

| Property                  | Type          | Description       |
| ------------------------- | ------------- | ----------------- |
| `cell.thumbnailImageView` | `UIImageView` | Campaign image    |
| `cell.nameLabel`          | `UILabel`     | Campaign name     |
| `cell.rewardLabel`        | `UILabel`     | Reward value      |
| `cell.currencyImageView`  | `UIImageView` | Currency icon     |
| `cell.promoBadge`         | `PaddedLabel` | Promo badge       |
| `cell.progressBadge`      | `PaddedLabel` | In Progress badge |

All subviews are `public` on `MCDefaultAdCell`; cast the cell with `cell as? MCDefaultAdCell` to access them.

***

### 4. Custom Loading View

By default, the SDK shows a pulsing skeleton placeholder while loading:

<figure><img src="/files/Ob59btVGjMZ5IbhiVRdA" alt="" width="134"><figcaption></figcaption></figure>

You can replace it:

```swift
// Simple spinner
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
adView.setLoadingView(spinner)
```

Or something more complex with text as well:

```swift
// Spinner + text, centered
let loading = UIView()
let stack = UIStackView()
stack.axis = .vertical
stack.alignment = .center
stack.spacing = 8
stack.translatesAutoresizingMaskIntoConstraints = false
loading.addSubview(stack)

let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
stack.addArrangedSubview(spinner)

let label = UILabel()
label.text = "Loading offers..."
label.textColor = .gray
stack.addArrangedSubview(label)

NSLayoutConstraint.activate([
    stack.centerXAnchor.constraint(equalTo: loading.centerXAnchor),
    stack.centerYAnchor.constraint(equalTo: loading.centerYAnchor)
])

adView.setLoadingView(loading)
```

<figure><img src="/files/YU5J2duHb7zcIxxfm9Bk" alt="" width="170"><figcaption></figcaption></figure>

Pass `nil` to restore the default skeleton:

```swift
adView.setLoadingView(nil)
```

***

### 5. Custom Click Handler

Override the default click behavior per-view:

```swift
adView.onCampaignClick = { campaign, position in
    // Your custom logic: show a dialog, navigate, etc.
    MCOfferwallSDK.shared.onClick(campaign: campaign, from: self) // or handle it yourself
}
```

> **Tip:** You can configure the default click behavior globally without writing a custom click handler. Call `MCOfferwallSDK.shared.setOpenInApp(true)` to open campaign details in an in-app WebView instead of Safari. See the Open Campaign Details In-App.

***

### 6. Loading Lifecycle Listener

Monitor loading state for your own UI logic:

```swift
adView.loadingListener = MCNativeAdView.LoadingListener(
    onLoadingStarted: {
        // Called when load() begins
    },
    onCampaignsLoaded: { count in
        // Called when campaigns arrive (count may be 0)
    },
    onError: { error in
        // Called if the fetch fails
    }
)
```

***

### Next Steps

* Need a completely different card design? See [Custom Layouts →](/ios/sdk-native/sdk-native-custom-layout)
* Need the full data reference? See [Data Reference →](/ios/sdk-native/sdk-native-data-reference)


# SDK Native - Custom Layout

## Native SDK — Custom Layouts

> **Prerequisite:** Familiar with the [Customizations](/ios/sdk-native/sdk-native-customizations) options.

When the default layout doesn't fit your design, you can provide a completely custom cell class. You control every pixel — the SDK only handles data fetching, impression tracking, and click handling.

***

### How It Works

You provide an implementation of `MCNativeAdRenderer` — a protocol with some mandatory methods plus some optional ones:

<table><thead><tr><th width="358">Method</th><th>What it does</th></tr></thead><tbody><tr><td><code>cellReuseIdentifier</code></td><td>A unique reuse identifier for your cell class</td></tr><tr><td><code>register(with:)</code></td><td>Registers your <code>UICollectionViewCell</code> subclass on the collection view</td></tr><tr><td><code>dequeueCell(collectionView:indexPath:)</code></td><td>Dequeues a cell using the reuse identifier</td></tr><tr><td><code>configure(cell:campaign:at:)</code></td><td>Binds campaign data to your cell — you decide what to show. Always called on the main thread.</td></tr><tr><td><code>itemSize(in:)</code></td><td>Returns the size of a single card</td></tr><tr><td><code>itemSize(in:for:)</code> <em>(optional)</em></td><td>Per-campaign size override. Default returns <code>itemSize(in:)</code>. Override when cells need different heights based on content (e.g. an "In Progress" badge that changes the card height).</td></tr><tr><td><code>lineSpacing</code> <em>(optional)</em></td><td>Spacing between consecutive items along the scroll axis. Default: 12pt.</td></tr></tbody></table>

The SDK handles everything else: fetching, scrolling, impression tracking, click handling.

Here we present three different custom layout examples that you can use as inspiration for your implementation. These are just examples — you can customize the layout however you'd like.

***

### Example A: Circular Thumbnails

This example creates a horizontal scroll of campaigns with circular images, promo badges, in-progress indicators, and a currency icon.

<figure><img src="/files/nHpQ1a5KiyzGr9VhhzN7" alt="" width="340"><figcaption></figcaption></figure>

#### Step A.1: Create the cell class

```swift
import UIKit
import MyChipsSdk

final class CircularCell: UICollectionViewCell {
    let thumbnailImageView = UIImageView()
    let nameLabel = UILabel()
    let currencyImageView = UIImageView()
    let rewardLabel = UILabel()
    let promoBadge = PaddedLabel()
    let progressBadge = PaddedLabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) { fatalError() }

    private func setup() {
        contentView.clipsToBounds = false
        clipsToBounds = false

        // Circular thumbnail on a light grey background
        thumbnailImageView.contentMode = .scaleAspectFill
        thumbnailImageView.clipsToBounds = true
        thumbnailImageView.layer.cornerRadius = 70
        thumbnailImageView.backgroundColor = UIColor(white: 0.91, alpha: 1)
        thumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(thumbnailImageView)

        nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        // Horizontal cards sit directly on the host's background, so use
        // `.label` to stay readable in both light and dark system themes.
        nameLabel.textColor = .label
        nameLabel.textAlignment = .center
        nameLabel.numberOfLines = 2
        nameLabel.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(nameLabel)

        // Reward row: currency icon + reward label, centered
        let rewardRow = UIStackView()
        rewardRow.axis = .horizontal
        rewardRow.spacing = 4
        rewardRow.alignment = .center
        rewardRow.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(rewardRow)

        currencyImageView.contentMode = .scaleAspectFit
        currencyImageView.translatesAutoresizingMaskIntoConstraints = false
        rewardRow.addArrangedSubview(currencyImageView)

        rewardLabel.font = .systemFont(ofSize: 16, weight: .bold)
        rewardLabel.textColor = .label
        rewardRow.addArrangedSubview(rewardLabel)

        // Promo badge (top-left overlap)
        promoBadge.backgroundColor = UIColor(red: 0xD6/255, green: 0x2C/255, blue: 0x1F/255, alpha: 1)
        promoBadge.textColor = .white
        promoBadge.font = .systemFont(ofSize: 12, weight: .bold)
        promoBadge.layer.cornerRadius = 12
        promoBadge.layer.masksToBounds = true
        promoBadge.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(promoBadge)

        // In Progress badge (below reward)
        progressBadge.text = "In Progress"
        progressBadge.backgroundColor = UIColor(red: 0xE1/255, green: 0xE5/255, blue: 0xEB/255, alpha: 1)
        progressBadge.textColor = UIColor(red: 0x42/255, green: 0x4B/255, blue: 0x5A/255, alpha: 1)
        progressBadge.font = .systemFont(ofSize: 10, weight: .semibold)
        progressBadge.textAlignment = .center
        progressBadge.layer.cornerRadius = 9
        progressBadge.layer.masksToBounds = true
        progressBadge.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(progressBadge)

        NSLayoutConstraint.activate([
            thumbnailImageView.topAnchor.constraint(equalTo: contentView.topAnchor),
            thumbnailImageView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            thumbnailImageView.widthAnchor.constraint(equalToConstant: 140),
            thumbnailImageView.heightAnchor.constraint(equalToConstant: 140),

            nameLabel.topAnchor.constraint(equalTo: thumbnailImageView.bottomAnchor, constant: 8),
            nameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            nameLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),

            rewardRow.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 6),
            rewardRow.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),

            currencyImageView.widthAnchor.constraint(equalToConstant: 16),
            currencyImageView.heightAnchor.constraint(equalToConstant: 16),

            promoBadge.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -6),
            promoBadge.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: -6),
            promoBadge.heightAnchor.constraint(equalToConstant: 25),

            progressBadge.topAnchor.constraint(equalTo: rewardRow.bottomAnchor, constant: 6),
            progressBadge.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            progressBadge.heightAnchor.constraint(equalToConstant: 18)
        ])
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        thumbnailImageView.image = nil
        currencyImageView.image = nil
        nameLabel.text = nil
        rewardLabel.text = nil
        promoBadge.isHidden = true
        progressBadge.isHidden = true
    }
}
```

#### Step A.2: Set the renderer

Implement `MCNativeAdRenderer` and hand it to the ad view.

Remember to replace `https://my-cdn/my-coin-image.png` with your actual icon.

```swift
final class CircularRenderer: MCNativeAdRenderer {
    var cellReuseIdentifier: String { "CircularCell" }

    func register(with collectionView: UICollectionView) {
        collectionView.register(CircularCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
    }

    func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
    }

    func configure(cell: UICollectionViewCell, campaign: MCCampaign, at index: Int) {
        guard let cell = cell as? CircularCell else { return }
        cell.nameLabel.text = campaign.name
        cell.rewardLabel.text = MCDefaultAdRenderer.formatReward(campaign.totalConvertedValue)

        // Thumbnail (fallback to cover)
        let url = (campaign.creatives?.thumbnail?.isEmpty == false)
            ? campaign.creatives?.thumbnail
            : campaign.creatives?.cover
        MCOfferwallSDK.shared.loadImage(url: url, into: cell.thumbnailImageView)

        // Currency icon — replace with your URL
        MCOfferwallSDK.shared.loadImage(url: "https://my-cdn/my-coin-image.png",
                                        into: cell.currencyImageView)

        // Promo badge
        if campaign.promoRatio > 1.0 {
            cell.promoBadge.isHidden = false
            cell.promoBadge.text = MCDefaultAdRenderer.formatPromo(campaign.promoRatio)
        } else {
            cell.promoBadge.isHidden = true
        }

        // In Progress badge (hide when completed / closed)
        let status = campaign.progress?.status
        let hasProgress = status != nil
            && status != MCCampaignStatus.completed
            && status != MCCampaignStatus.closed
        cell.progressBadge.isHidden = !hasProgress
    }

    func itemSize(in collectionView: UICollectionView) -> CGSize {
        CGSize(width: 140, height: 240)
    }
}

// Usage
adView.setRenderer(CircularRenderer())
adView.load()
```

***

### Example B: Vertical Layout (small cards)

This example renders a vertical list of compact row cards. Each item shows a rounded thumbnail on the left, the title and an "In Progress" badge in the center, and the reward with currency icon on the right. The promo badge floats above the top-right corner of the card.

<figure><img src="/files/cquHNqYYFwUo3eH5yOim" alt="" width="308"><figcaption></figcaption></figure>

#### Step B.1: Create the cell class

```swift
final class SmallCampaignCell: UICollectionViewCell {
    let thumbnailImageView = UIImageView()
    let nameLabel = UILabel()
    let progressBadge = PaddedLabel()
    let currencyImageView = UIImageView()
    let rewardLabel = UILabel()
    let promoBadge = PaddedLabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) { fatalError() }

    private func setup() {
        contentView.clipsToBounds = false
        clipsToBounds = false

        let card = UIView()
        card.backgroundColor = .white
        card.layer.cornerRadius = 12
        card.layer.borderColor = UIColor(white: 0.88, alpha: 1).cgColor
        card.layer.borderWidth = 1
        card.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(card)

        thumbnailImageView.contentMode = .scaleAspectFill
        thumbnailImageView.clipsToBounds = true
        thumbnailImageView.layer.cornerRadius = 12
        thumbnailImageView.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(thumbnailImageView)

        nameLabel.font = .systemFont(ofSize: 14, weight: .semibold)
        nameLabel.textColor = .black
        nameLabel.numberOfLines = 1

        progressBadge.text = "In Progress"
        progressBadge.backgroundColor = UIColor(red: 0xE1/255, green: 0xE5/255, blue: 0xEB/255, alpha: 1)
        progressBadge.textColor = UIColor(red: 0x42/255, green: 0x4B/255, blue: 0x5A/255, alpha: 1)
        progressBadge.font = .systemFont(ofSize: 10, weight: .semibold)
        progressBadge.layer.cornerRadius = 9
        progressBadge.layer.masksToBounds = true
        progressBadge.heightAnchor.constraint(equalToConstant: 18).isActive = true

        // Title + progress in a stack so the badge collapses when hidden
        let textStack = UIStackView(arrangedSubviews: [nameLabel, progressBadge])
        textStack.axis = .vertical
        textStack.alignment = .leading
        textStack.spacing = 4
        textStack.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(textStack)

        currencyImageView.contentMode = .scaleAspectFit
        currencyImageView.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(currencyImageView)

        rewardLabel.font = .systemFont(ofSize: 16, weight: .bold)
        rewardLabel.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(rewardLabel)

        promoBadge.backgroundColor = UIColor(red: 0xD6/255, green: 0x2C/255, blue: 0x1F/255, alpha: 1)
        promoBadge.textColor = .white
        promoBadge.font = .systemFont(ofSize: 12, weight: .bold)
        promoBadge.layer.cornerRadius = 10
        promoBadge.layer.masksToBounds = true
        promoBadge.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(promoBadge)

        NSLayoutConstraint.activate([
            card.topAnchor.constraint(equalTo: contentView.topAnchor),
            card.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            card.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            card.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),

            thumbnailImageView.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
            thumbnailImageView.centerYAnchor.constraint(equalTo: card.centerYAnchor),
            thumbnailImageView.widthAnchor.constraint(equalToConstant: 60),
            thumbnailImageView.heightAnchor.constraint(equalToConstant: 60),

            textStack.leadingAnchor.constraint(equalTo: thumbnailImageView.trailingAnchor, constant: 12),
            textStack.centerYAnchor.constraint(equalTo: card.centerYAnchor),
            textStack.trailingAnchor.constraint(lessThanOrEqualTo: currencyImageView.leadingAnchor, constant: -8),

            rewardLabel.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
            rewardLabel.centerYAnchor.constraint(equalTo: card.centerYAnchor),

            currencyImageView.trailingAnchor.constraint(equalTo: rewardLabel.leadingAnchor, constant: -4),
            currencyImageView.centerYAnchor.constraint(equalTo: card.centerYAnchor),
            currencyImageView.widthAnchor.constraint(equalToConstant: 18),
            currencyImageView.heightAnchor.constraint(equalToConstant: 18),

            promoBadge.topAnchor.constraint(equalTo: contentView.topAnchor, constant: -10),
            promoBadge.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -12),
            promoBadge.heightAnchor.constraint(equalToConstant: 20)
        ])
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        thumbnailImageView.image = nil
        currencyImageView.image = nil
        nameLabel.text = nil
        rewardLabel.text = nil
        promoBadge.isHidden = true
        progressBadge.isHidden = true
    }
}
```

#### Step B.2: Wire the renderer

Set the orientation to vertical and provide an `MCNativeAdRenderer` that returns the cell above. Because the promo badge overlaps the top edge, override `lineSpacing` to add breathing room.

Also remember to replace `https://your.cdn/currency.png` with your actual currency icon.&#x20;

```swift
final class SmallRenderer: MCNativeAdRenderer {
    var cellReuseIdentifier: String { "SmallCampaignCell" }

    // Extra breathing room so the promo badge, which overlaps the top edge by
    // 10pt, doesn't bleed onto the previous card.
    var lineSpacing: CGFloat { 24 }

    func register(with collectionView: UICollectionView) {
        collectionView.register(SmallCampaignCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
    }

    func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
    }

    func configure(cell: UICollectionViewCell, campaign: MCCampaign, at index: Int) {
        guard let cell = cell as? SmallCampaignCell else { return }
        cell.nameLabel.text = campaign.name
        cell.rewardLabel.text = MCDefaultAdRenderer.formatReward(campaign.totalConvertedValue)

        let url = (campaign.creatives?.thumbnail?.isEmpty == false)
            ? campaign.creatives?.thumbnail
            : campaign.creatives?.cover
        MCOfferwallSDK.shared.loadImage(url: url, into: cell.thumbnailImageView)
        MCOfferwallSDK.shared.loadImage(url: "https://your.cdn/currency.png",
                                        into: cell.currencyImageView)

        if campaign.promoRatio > 1.0 {
            cell.promoBadge.isHidden = false
            cell.promoBadge.text = MCDefaultAdRenderer.formatPromo(campaign.promoRatio)
        } else {
            cell.promoBadge.isHidden = true
        }

        let status = campaign.progress?.status
        let hasProgress = status != nil
            && status != MCCampaignStatus.completed
            && status != MCCampaignStatus.closed
        cell.progressBadge.isHidden = !hasProgress
    }

    func itemSize(in collectionView: UICollectionView) -> CGSize {
        CGSize(width: collectionView.bounds.width - 32, height: 92)
    }
}

// Usage
adView.setOrientation(.vertical)
adView.setRenderer(SmallRenderer())
adView.load()
```

***

### Example C: Vertical Layout (big cards)

This example creates a vertical list of large cover cards. Each item shows a wide cover image at the top, and a full-width green reward pill at the bottom.

<figure><img src="/files/GfXwyvJFqSnUsnDQ9oLr" alt="" width="311"><figcaption></figcaption></figure>

#### Step C.1: Create the cell class

```swift
final class CoverCell: UICollectionViewCell {
    let coverImageView = UIImageView()
    let nameLabel = UILabel()
    let progressBadge = PaddedLabel()
    let rewardPill = UIView()
    let rewardLabel = UILabel()
    let currencyImageView = UIImageView()
    let promoBadge = PaddedLabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder: NSCoder) { fatalError() }

    private func setup() {
        let card = UIView()
        card.backgroundColor = .white
        card.layer.cornerRadius = 12
        card.layer.borderColor = UIColor(white: 0.88, alpha: 1).cgColor
        card.layer.borderWidth = 1
        card.clipsToBounds = true
        card.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(card)

        coverImageView.contentMode = .scaleAspectFill
        coverImageView.clipsToBounds = true
        coverImageView.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(coverImageView)

        nameLabel.font = .systemFont(ofSize: 16, weight: .bold)
        nameLabel.textColor = .black
        nameLabel.numberOfLines = 1

        progressBadge.text = "In Progress"
        progressBadge.backgroundColor = UIColor(red: 0xE1/255, green: 0xE5/255, blue: 0xEB/255, alpha: 1)
        progressBadge.textColor = UIColor(red: 0x42/255, green: 0x4B/255, blue: 0x5A/255, alpha: 1)
        progressBadge.font = .systemFont(ofSize: 10, weight: .semibold)
        progressBadge.layer.cornerRadius = 9
        progressBadge.layer.masksToBounds = true
        progressBadge.heightAnchor.constraint(equalToConstant: 18).isActive = true

        let textStack = UIStackView(arrangedSubviews: [nameLabel, progressBadge])
        textStack.axis = .vertical
        textStack.alignment = .leading
        textStack.spacing = 6
        textStack.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(textStack)

        rewardPill.backgroundColor = UIColor(red: 0x4C/255, green: 0xAF/255, blue: 0x50/255, alpha: 1)
        rewardPill.layer.cornerRadius = 23.5
        rewardPill.translatesAutoresizingMaskIntoConstraints = false
        card.addSubview(rewardPill)

        currencyImageView.contentMode = .scaleAspectFit
        currencyImageView.translatesAutoresizingMaskIntoConstraints = false
        rewardPill.addSubview(currencyImageView)

        rewardLabel.font = .systemFont(ofSize: 18, weight: .bold)
        rewardLabel.textColor = .white
        rewardLabel.translatesAutoresizingMaskIntoConstraints = false
        rewardPill.addSubview(rewardLabel)

        // Promo badge sits inside the cover, bottom-right
        promoBadge.backgroundColor = UIColor(red: 0xD6/255, green: 0x2C/255, blue: 0x1F/255, alpha: 1)
        promoBadge.textColor = .white
        promoBadge.font = .systemFont(ofSize: 12, weight: .bold)
        promoBadge.layer.cornerRadius = 12
        promoBadge.layer.masksToBounds = true
        promoBadge.layer.borderColor = UIColor.white.cgColor
        promoBadge.layer.borderWidth = 2
        promoBadge.translatesAutoresizingMaskIntoConstraints = false
        coverImageView.addSubview(promoBadge)
        coverImageView.isUserInteractionEnabled = true

        NSLayoutConstraint.activate([
            card.topAnchor.constraint(equalTo: contentView.topAnchor),
            card.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            card.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            card.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),

            coverImageView.topAnchor.constraint(equalTo: card.topAnchor),
            coverImageView.leadingAnchor.constraint(equalTo: card.leadingAnchor),
            coverImageView.trailingAnchor.constraint(equalTo: card.trailingAnchor),
            coverImageView.heightAnchor.constraint(equalToConstant: 172),

            textStack.topAnchor.constraint(equalTo: coverImageView.bottomAnchor, constant: 10),
            textStack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 12),
            textStack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -12),

            rewardPill.topAnchor.constraint(equalTo: textStack.bottomAnchor, constant: 10),
            rewardPill.bottomAnchor.constraint(lessThanOrEqualTo: card.bottomAnchor, constant: -12),
            rewardPill.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 12),
            rewardPill.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -12),
            rewardPill.heightAnchor.constraint(equalToConstant: 47),

            currencyImageView.leadingAnchor.constraint(equalTo: rewardPill.leadingAnchor, constant: 16),
            currencyImageView.centerYAnchor.constraint(equalTo: rewardPill.centerYAnchor),
            currencyImageView.widthAnchor.constraint(equalToConstant: 20),
            currencyImageView.heightAnchor.constraint(equalToConstant: 20),

            rewardLabel.leadingAnchor.constraint(equalTo: currencyImageView.trailingAnchor, constant: 8),
            rewardLabel.centerYAnchor.constraint(equalTo: rewardPill.centerYAnchor),

            promoBadge.bottomAnchor.constraint(equalTo: coverImageView.bottomAnchor, constant: -12),
            promoBadge.trailingAnchor.constraint(equalTo: coverImageView.trailingAnchor, constant: -12),
            promoBadge.heightAnchor.constraint(equalToConstant: 25)
        ])
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        coverImageView.image = nil
        currencyImageView.image = nil
        nameLabel.text = nil
        rewardLabel.text = nil
        promoBadge.isHidden = true
        progressBadge.isHidden = true
    }
}
```

#### Step C.2: Wire the renderer

The cell height changes depending on whether the "In Progress" badge is shown, so override `itemSize(in:for:)` to return a per-campaign size.

Also remember to replace `https://your.cdn/currency.png` with your actual currency icon.&#x20;

```swift
final class CoverRenderer: MCNativeAdRenderer {
    var cellReuseIdentifier: String { "CoverCell" }

    func register(with collectionView: UICollectionView) {
        collectionView.register(CoverCell.self, forCellWithReuseIdentifier: cellReuseIdentifier)
    }

    func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseIdentifier, for: indexPath)
    }

    func configure(cell: UICollectionViewCell, campaign: MCCampaign, at index: Int) {
        guard let cell = cell as? CoverCell else { return }

        // Cover (fallback to thumbnail)
        let coverUrl = (campaign.creatives?.cover?.isEmpty == false)
            ? campaign.creatives?.cover
            : campaign.creatives?.thumbnail
        MCOfferwallSDK.shared.loadImage(url: coverUrl, into: cell.coverImageView)
        MCOfferwallSDK.shared.loadImage(url: "https://your.cdn/currency.png",
                                        into: cell.currencyImageView)

        cell.nameLabel.text = campaign.name
        cell.rewardLabel.text = MCDefaultAdRenderer.formatReward(campaign.totalConvertedValue)

        if campaign.promoRatio > 1.0 {
            cell.promoBadge.isHidden = false
            cell.promoBadge.text = MCDefaultAdRenderer.formatPromo(campaign.promoRatio)
        } else {
            cell.promoBadge.isHidden = true
        }

        let status = campaign.progress?.status
        let hasProgress = status != nil
            && status != MCCampaignStatus.completed
            && status != MCCampaignStatus.closed
        cell.progressBadge.isHidden = !hasProgress
    }

    func itemSize(in collectionView: UICollectionView) -> CGSize {
        CGSize(width: collectionView.bounds.width - 32, height: 300)
    }

    func itemSize(in collectionView: UICollectionView, for campaign: MCCampaign) -> CGSize {
        let status = campaign.progress?.status
        let hasProgress = status != nil
            && status != MCCampaignStatus.completed
            && status != MCCampaignStatus.closed
        return CGSize(width: collectionView.bounds.width - 32,
                      height: hasProgress ? 300 : 272)
    }
}

// Usage
adView.setOrientation(.vertical)
adView.setRenderer(CoverRenderer())
adView.load()
```

***

### Key Points for Custom Layouts

#### Click Handling

The SDK wires click handlers **automatically** — tapping any item opens the campaign's detail page. You can override this with `adView.onCampaignClick`.

#### Image Loading

Use the SDK's built-in image loader in your renderer:

```swift
MCOfferwallSDK.shared.loadImage(url: url, into: imageView)
```

It handles background downloading, `UIImage` decoding, and cell recycling. No external library needed.

#### Cell Recycling Tip

Always reset images and hidden-state views in `prepareForReuse()`. The SDK does not restrict your cell class in any way — it just dequeues and calls `configure(cell:campaign:at:)`.

***

### Next Steps

* Need the full data reference? See [Data Reference →](/ios/sdk-native/sdk-native-data-reference)
* Need simpler tweaks? See [Simple Customization →](/ios/sdk-native/sdk-native-customizations)


# SDK Native - Data Reference

## Native SDK — Data Reference

Complete reference for all data objects available in the native campaigns SDK.

***

<figure><img src="/files/ugYWrFGsyJVza7CrPZC1" alt=""><figcaption></figcaption></figure>

### MCCampaign

The main campaign object passed to your renderer's `configure(cell:campaign:at:)`.

<table><thead><tr><th width="232">Field</th><th width="176">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td><code>String</code></td><td>Unique campaign identifier</td></tr><tr><td><code>name</code></td><td><code>String</code></td><td>Campaign display name</td></tr><tr><td><code>type</code></td><td><code>String</code></td><td>Campaign type. See <code>MCCampaignType</code> constants below.</td></tr><tr><td><code>creatives</code></td><td><code>MCCreatives?</code></td><td>Image URLs (see below)</td></tr><tr><td><code>links</code></td><td><code>MCLinks?</code></td><td>Tracking and navigation URLs (see below)</td></tr><tr><td><code>totalConvertedValue</code></td><td><code>Double</code></td><td>Total reward in your virtual currency (including promo)</td></tr><tr><td><code>remainingConvertedValue</code></td><td><code>Double</code></td><td>Remaining reward the user can earn</td></tr><tr><td><code>promoRatio</code></td><td><code>Double</code></td><td>Promotional multiplier (1.0 = no promo, 2.0 = double)</td></tr><tr><td><code>progress</code></td><td><code>MCProgress?</code></td><td>User's progress. <strong><code>nil</code></strong> if the user hasn't started.</td></tr></tbody></table>

***

### MCCreatives

Image assets for a campaign.

<table><thead><tr><th width="211">Field</th><th width="192">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>thumbnail</code></td><td><code>String?</code></td><td>Small square preview image URL</td></tr><tr><td><code>cover</code></td><td><code>String?</code></td><td>Full-size banner image URL</td></tr></tbody></table>

***

### MCLinks

Tracking and navigation URLs. Managed automatically by the SDK when using `MCNativeAdView`.

<table><thead><tr><th width="202">Field</th><th width="113">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>trackingUrl</code></td><td><code>String?</code></td><td>Click tracking URL</td></tr><tr><td><code>trackingPixelUrl</code></td><td><code>String?</code></td><td>Impression pixel URL (fired automatically by the SDK)</td></tr><tr><td><code>detailUrl</code></td><td><code>String?</code></td><td>Campaign detail page URL (opened on click)</td></tr></tbody></table>

***

### MCProgress

User's progress on a campaign. **`nil`** when the user hasn't started the campaign.

<table><thead><tr><th width="170">Field</th><th width="147">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td><code>String?</code></td><td>Progress status. See <code>MCCampaignStatus</code> constants below.</td></tr><tr><td><code>eventsCompleted</code></td><td><code>Int</code></td><td>Number of events the user has completed</td></tr><tr><td><code>totalEvents</code></td><td><code>Int</code></td><td>Total events required to complete the campaign</td></tr><tr><td><code>valueEarned</code></td><td><code>Double</code></td><td>Reward earned so far in your virtual currency</td></tr><tr><td><code>progressValue</code></td><td><code>Double</code></td><td>Completion percentage (0–100)</td></tr></tbody></table>

***

### MCMeta

Response metadata returned alongside the campaign list.

<table><thead><tr><th width="155">Field</th><th width="168">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>version</code></td><td><code>String</code></td><td>API response format version</td></tr><tr><td><code>count</code></td><td><code>Int</code></td><td>Number of campaigns returned</td></tr></tbody></table>

***

### MCCampaignType (constants)

Known campaign type values. Compare against `campaign.type`:

<table><thead><tr><th width="336">Constant</th><th>Value</th></tr></thead><tbody><tr><td><code>MCCampaignType.multiReward</code></td><td><code>"MultiReward"</code></td></tr><tr><td><code>MCCampaignType.playToEarn</code></td><td><code>"Play2Earn"</code></td></tr><tr><td><code>MCCampaignType.singleReward</code></td><td><code>"SingleReward"</code></td></tr></tbody></table>

```swift
if campaign.type == MCCampaignType.multiReward {
    // Handle multi-reward campaign
}
```

> The `type` field is a `String`, not an enum, because new types may be added in the future. Unknown types will not break your app.

***

### MCCampaignStatus (constants)

Known progress status values. Compare against `campaign.progress?.status`:

<table><thead><tr><th width="310">Constant</th><th>Value</th></tr></thead><tbody><tr><td><code>MCCampaignStatus.clicked</code></td><td><code>"clicked"</code></td></tr><tr><td><code>MCCampaignStatus.started</code></td><td><code>"started"</code></td></tr><tr><td><code>MCCampaignStatus.installed</code></td><td><code>"installed"</code></td></tr><tr><td><code>MCCampaignStatus.completed</code></td><td><code>"completed"</code></td></tr><tr><td><code>MCCampaignStatus.expired</code></td><td><code>"expired"</code></td></tr><tr><td><code>MCCampaignStatus.closed</code></td><td><code>"closed"</code></td></tr></tbody></table>

```swift
if let status = campaign.progress?.status, status == MCCampaignStatus.completed {
    // Campaign completed!
}
```

> The `status` field is a `String`, not an enum, because new statuses may be added in the future.

***

### Image Loading

The SDK provides a built-in image loader. Use it anywhere in your renderer:

```swift
MCOfferwallSDK.shared.loadImage(url: url, into: imageView)
```

* Runs on a background thread
* Decodes the image and sets it on the main thread
* Handles cell view recycling correctly
* No external library needed (no SDWebImage, Kingfisher, or Nuke)

> You can also use your own image loading library if you prefer. The SDK does not restrict this.

***

### External References

For a more comprehensive list of examples and mappings, you can also check out the [Figma Showcase](https://www.figma.com/board/7ctPofi9irp7PV4TwT3a7l/MyChips-SDK-Default-Layouts?node-id=0-1\&t=Ej26WBktLfW0wu3A-1) of different templates.


# Reward User

#### 1. Fully Managed by MyChips

{% hint style="info" %}
Use this method only if you have selected Self-Managed Currency. If you already support S2S postback please skip this snippet.
{% endhint %}

To check for a reward use the getReward function. It requires your adunitId, a reward callback(which will be called if the checks shows that there is a reward), and an on error callback.

```swift
MCOfferwallSDK.shared.getReward(adunitId: "YOUR_AD_UNIT_ID") { reward in
        //do something with the reward, here we just print it
        print(reward.virtualCurrencyReward)
    } onError: { error in
        //do something with the error, here we just print it
        print(error)
    }

```

> If there is no reward and no error, nothing will happen

You should check for the reward whenever the app is started or resumed. Example integration:

{% tabs %}
{% tab title="SwiftUI" %}

<pre class="language-swift"><code class="lang-swift"><strong>// Import necessary modules for SwiftUI and the custom SDK.
</strong>import SwiftUI
import MyChipsSdk

// Main entry point of the SwiftUI app, marked with @main attribute.
@main
struct MyChipsSdkSwiftUIExampleApp: App {
    
    // Access the current scene phase (e.g., active, inactive, background) using @Environment.
    @Environment(\.scenePhase) var scenePhase
    
    // State variable to track whether the app is being activated for the first time.
    @State private var firstActive = true
    
    // Define the body property, specifying the app's main scene.
    var body: some Scene {
        // The main window group of the app.
        WindowGroup {
            // The root view of the app is ContentView.
            ContentView()
                // Perform actions when the scene phase changes (e.g., active, inactive, background).
                .onChange(of: scenePhase, perform: { phase in
                    // Check if the app is currently active.
                    if phase == .active {
                        // If this is the first activation (app startup):
                        if firstActive {
                            // Initialize the SDK with the API key.
                            MCOfferwallSDK.shared.configure(apiKey: "YOUR_API_KEY")
                            
                            // Optionally set a user ID for tracking or personalization.
                            MCOfferwallSDK.shared.setUserId(userId: "YOUR_USER_ID")
                            
                            // Mark firstActive as false so this block won't execute again.
                            firstActive = false
                        }
                        
                        // Check for any rewards when the app starts or resumes.
                        MCOfferwallSDK.shared.getReward(
                            adunitId: "YOUR_AD_UNIT_ID") { reward in
                                // Handle successful reward retrieval by printing the reward amount.
                                print(reward.virtualCurrencyReward)
                            } onError: { error in
                                // Handle any errors encountered during reward retrieval.
                                print(error)
                            }
                    }
                })
        }
    }
}

</code></pre>

{% endtab %}

{% tab title="UIKit" %}

```swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    
    var firstActive = true
    func sceneDidBecomeActive(_ scene: UIScene) {
        if (firstActive) {
            //at the app start initialize the sdk
            let _ = MCOfferwallSDK.shared.configure(apiKey: "YOUR_API_KEY")
            //optionally set user id
            MCOfferwallSDK.shared.setUserId(userId: "YOUR_USER_ID")
            firstActive = false
        }
        
        //other the start or resume check for the reward and do something
        MCOfferwallSDK.shared.getReward(
            adunitId: "YOUR_AD_UNIT_ID") { reward in
                print(reward.virtualCurrencyReward)
            } onError: { error in
                print(error)
            }
    }
    
    //...OTHER SCENEDELEGATE FUNCTIONS

}

```

{% endtab %}
{% endtabs %}

#### 2. Server-to-Server (S2S) Postbacks

If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# Flutter

<details>

<summary>Release Note</summary>

### **Version:** 1.0.5 (current)

Date: 2026-02-13

**New Feature:**\
Added support for an optional top bar with a close button on the Offerwall page.

The top bar is disabled by default and can be enabled by setting `showTopBar: true` when opening the `OfferwallPage`.

</details>

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/c1CzrPXm55XO9p2hex1s" %}
[Install SDK](/flutter/install-sdk)
{% endcontent-ref %}

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/B8OpqYNntUr1bdoEU3jQ" %}
[Reward User](/flutter/reward-user)
{% endcontent-ref %}


# Install SDK

## 1. Introduction

This guide provides a comprehensive walkthrough for integrating the MyChips SDK into your Flutter application, enabling the display of an engaging offerwall.

## 2. SDK Integration

To add the package to your project folder using the terminal:

```shell
 $ flutter pub add my_chips_flutter_sdk
```

Now in your main.dart code, you can import it:

```dart
import 'package:my_chips_flutter_sdk/my_chips_flutter_sdk.dar
```

## 3. Initializating the SDK

Before using the sdk or showing the offerwall you must initialize it:

```dart
//replace "YOUR_API_KEY"
await MCOfferwallSdk.instance.init("YOUR_API_KEY");
```

> Obtain your API key and User ID from [Universal Developer Portal](https://dashboard.maf.ad)

#### **3.1 (Mandatory)** – Set **Google Advertising ID (Android)** and **Identifier for Advertisers (iOS)**

Improve reward tracking and eCPM performance by passing the Google Advertising ID ([Official documentation](https://developer.android.com/training/articles/ad-id)) for Android devices and the IDFA for iOS devices.

**Android:**

```dart
await MCOfferwallSdk.instance.setGaid("YOUR_GAID");
```

Replace "YOUR\_GAID" with your actual Google Advertising ID variable or value.

**iOS:**

```dart
await MCOfferwallSdk.instance.setIdfa("YOUR_IDFA");
```

Replace "YOUR-IDFA" with your actual IDFA variable or value.

#### 3.2 (Optional) Set userId if you have your own unique id

```dart
//replace "YOUR_USER_ID"
await MCOfferwallSdk.instance.setUserId("YOUR_USER_ID"); 
```

&#x20;   Replace `"`YOUR\_USER\_ID`"` with your actual user ID variable or value.

&#x20;   If you do not provide a specific user ID, one will be automatically generated.

#### 3.3 (Optional) Set User Age

You can set the user’s age to help improve ad targeting and analytics.

```dart
 await MCOfferwallSdk.instance.setAge(30);
```

Replace 30 with your actual user age variable or value (integer).

💡 **Note:**

* The value should be an integer (e.g., 18, 25, 30).
* Expected range is 0–100 (inclusive).

#### 3.4 (Optional) Set User Gender

You can set the user’s gender to help improve ad targeting and analytics.

```dart
  await MCOfferwallSdk.instance.setGender(MCGenderEnum.male);
```

Available enum values:

```dart
  MCGenderEnum.male
  MCGenderEnum.female
  MCGenderEnum.other
```

#### 3.5 (Optional) Set Custom Parameters (`aff_sub1`–`aff_sub5` )

We provide 5 `aff_sub` parameters (`aff_sub1`, `aff_sub2`, `aff_sub3`, `aff_sub4`, `aff_sub5`), which you can use to pass custom values.

```dart
MCOfferwallSDK.setAffSub1("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub2("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub3("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub4("HERE YOUR CUSTOM VALUE");
MCOfferwallSDK.setAffSub5("HERE YOUR CUSTOM VALUE");
```

Replace `"`HERE YOUR CUSTOM VALUE`"` with your actual custom value.

## **4. Display the Offerwall**

To show the offerwall we provide you with a widget called OfferwallPage. You should show it as a page using your preferred navigation package/method.

Example using the default flutter navigator:

```dart
//replace "YOUR_AD_UNIT_ID"
Navigator.of(context).push(
    MaterialPageRoute(
        builder: (context) => OfferwallPage(
        adunitId: "YOUR_AD_UNIT_ID",
        ),
    ),
);
```

> Your Ad unit ID can be found at [here](http://dashboard.maf.ad)

#### Optional: Enable the Top Bar with Close Button

The Offerwall supports an optional top bar with a close button.\
This feature is **disabled by default** to avoid interfering with the host app’s UI.

To enable the top bar, set `showTopBar: true` when opening the Offerwall:

```dart
Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => OfferwallPage(
      adunitId: "YOUR_AD_UNIT_ID",
      showTopBar: true, // Enables top bar with close button
    ),
  ),
);
```


# Reward User

#### 1. Fully Managed by MyChips

{% hint style="info" %}
Use this method only if you have selected Self-Managed Currency. If you already support S2S postback please skip this snippet.
{% endhint %}

To check for a reward use the checkReward function. It requires your adUnitId, a reward callback (which will be called if the checks shows that there is a reward), and an on error callback.

```dart
MCOfferwallSdk.instance.checkReward(
    adUnitId: "YOUR_AD_UNIT_ID",
    onReward: (reward) {
        //do something with the reward, here we just print it out
        //Be cautious: reward.virtualCurrencyReward returns a Double value.
        print("Reward: ${reward.virtualCurrencyReward}");
    },
    onError: (e, st) {
        //do something on error, here we just print it out
        print("Error: $e");
        print("Stacktrace: $st");
    });
```

> If there is no reward and no error, nothing will happen

You should check for the reward whenever the app is started or resumed. Example integration:

```dart
void main() {
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  late final AppLifecycleListener _appLifecycleListener;

  @override
  void initState() {
    super.initState();
    initialize();
  }

  @override
  void dispose() {
    _appLifecycleListener.dispose();
    super.dispose();
  }

  Future<void> checkReward() async {
    MCOfferwallSdk.instance.checkReward(
      adUnitId: "YOUR_AD_UNIT_ID",
      onReward: (reward) {
        log("Reward: ${reward.virtualCurrencyReward}");
      },
      onError: (e, st) {
        log("Error: $e StackTrace: $st");
      },
    );
  }

  Future<void> initialize() async {
    //initialize the sdk and set the user id
    await MCOfferwallSdk.instance.init("YOUR_API_KEY");
    await MCOfferwallSdk.instance.setUserId("YOUR_USER_ID");

    //check for reward when the app is initialized
    checkReward();

    //check for reward when the app is resumed
    _appLifecycleListener = AppLifecycleListener(
      onResume: () {
        checkReward();
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return ...//your app body
  }
}

```

#### 2. Server-to-Server (S2S) Postbacks

If you prefer server-to-server communication, MyChips can send a postback to your server with bonus information. The configuration for postbacks is available in your publisher dashboard. This method is useful for validating and securely rewarding users without client-side manipulation.&#x20;

If you are testing in Sandbox mode, the value of the macro {user\_payout} will be 0.


# iFrame

Steps on how to integrate MyChips with iFrame integration (web integration).

#### 1. Include the following SDK script and initialize it within your HTML.

```
<script src="https://v1.mychips.io/SDK/latest/my-chips-sdk.bundle.js"></script>myChipsSDK('init', {userId: '{{user_id}}'})
```

#### 2. Add the iFrame code in your HTML.

**Example iFrame code for iOS users:**

```
<iframe id="my-chips-iframe" allow="ch-ua-platform; ch-ua-platform-version" src="https://sdk.mychips.io/content?content_id={adunit_id}&user_id={user_id}&idfa={idfa}&gender={gender}&age={age}"></iframe>
```

**Example iFrame code for ANDROID users:**

```
<iframe id="my-chips-iframe" allow="ch-ua-platform; ch-ua-platform-version" src="https://sdk.mychips.io/content?content_id={adunit_id}&user_id={user_id}&gaid={gaid}&gender={gender}&age={age}"></iframe>
```

**Note:** Replace all macro placeholders (e.g., `{user_id}`, `{adunit_id}`) in the iFrame code with your actual values.\
Also, make sure that any parameters you add to the URL from the[ parameters list](#the-list-of-parameters-available) below are updated with real data.

**Example iFrame code with values replaced for iOS users:**

```
<iframe id="my-chips-iframe" allow="ch-ua-platform; ch-ua-platform-version" src="https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&idfa=6D92078A-8246-4BA4-AE5B-76104861E7DC&gender=m&age=30"></iframe>
```

**Example iFrame code with values replaced for ANDROID users:**

```
<iframe id="my-chips-iframe" allow="ch-ua-platform; ch-ua-platform-version" src="https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&gaid=38400000-8cf0-11bd-b23e-10b96e40000d&gender=m&age=30"></iframe>
```

#### 3. Locating Your iFrame Code

To locate your iFrame code, please click on the Edit button shown in the screen below.

<figure><img src="/files/lBRMpR9B5qwKZLd21Z6B" alt=""><figcaption></figcaption></figure>

Next, navigate to the Integration section, where you'll find your iFrame code. The code highlighted in the green square is ready to use, and the content\_id parameter corresponds to your ad unit ID.

![](/files/z7pwZuAnJR3GzoW3w6dE)

***

In the list below, you'll find all the available parameters. If you wish to send any of these parameters to us, simply add them to the URL in the iframe code.

#### The list of parameters available:

<table data-header-hidden><thead><tr><th width="112.75"></th><th width="120.25"></th><th width="109.5"></th><th></th></tr></thead><tbody><tr><td><strong>Parameter</strong></td><td><strong>Example</strong></td><td>Mandatory</td><td>Notes</td></tr><tr><td>user_id</td><td>U9221</td><td>Yes</td><td>The User ID is a unique identifier assigned to an individual user. If no specific User ID is provided, the system will automatically generate one to ensure proper user identification and tracking.</td></tr><tr><td>gaid</td><td>38400000-8cf0-11bd-b23e-10b96e40000d</td><td>No</td><td>The Google Advertising ID (GAID) is a unique, randomly generated identifier assigned by Google to Android devices, which enables advertisers to track and measure users’ advertising interactions across apps and services. <strong>We highly recommend including the GAID parameter, as it helps improve eCPM performance.</strong></td></tr><tr><td>idfa</td><td>6D92078A-8246-4BA4-AE5B-76104861E7DC</td><td>No</td><td>The Identifier for Advertisers (IDFA) is a unique, randomly generated code that Apple assigns to each user’s device, allowing advertisers to monitor and analyze advertising-related activity. <strong>We highly recommend including the IDFA parameter, as it helps improve eCPM performance.</strong></td></tr><tr><td>click_id</td><td>6a204bd89f3</td><td>No</td><td>A unique clickid. Keep the same value at each events</td></tr><tr><td>gender</td><td>m</td><td>No</td><td>Supported values: “m” (male), “f” (female), “n” (not specified).</td></tr><tr><td>age</td><td>45</td><td>No</td><td>user age (numeric 0-100)</td></tr><tr><td>aff_sub1,<br>aff_sub2, aff_sub3, aff_sub4, aff_sub5</td><td></td><td>No</td><td>Custom parameters that can be passed and returned in the postback.</td></tr></tbody></table>

### Reward Users

The only reward option available on iFrame is the **Webhook S2S Postback**. Please check the relevant documentation below.

{% content-ref url="/spaces/8FPXPNk0nLIRsJsg3nxP/pages/f5Te1Ap2Kdp1eAOkbClA" %}
[Webhook S2S Postback](/reward-handling/webhook-s2s-postback)
{% endcontent-ref %}


# WebView & Direct Link

Below is the integration guide for WebView and Direct Link

Although we provide support for WebView or direct-link integration, **this is not the recommended approach**. Whenever possible, you should integrate our **official SDK** instead.

#### Using the SDK ensures:

* **Better tracking accuracy** — SDK handles device signals and user sessions more reliably
* **Stronger fraud protection** — Anti-fraud checks and security layers are baked into SDK flows
* **Automatic updates** — You benefit from improvements and bug fixes without touching your integration
* **Faster integration** — No need to manually handle WebView setup, edge cases, or command bridges

> WebView or direct links should only be used in special cases where SDK usage is not possible (e.g., HTML-based apps, low-code platforms). Even then, you must strictly follow the rules in this guide to ensure reliability.

Your offerwall can be integrated either with our SDK or by embedding the URL directly via WebView. If you’re using **WebView or direct links**, please follow the instructions below to avoid common issues.

## 1. Building the Offerwall URL

You **must** append correct parameters to ensure tracking and reward delivery.

**Base URL:**

```
https://sdk.mychips.io/content
```

**Required Parameters:**

* `content_id` – your assigned adunit ID
* `user_id` – a unique user ID on your side (can be a hash or UUID or numeric)
* `webview` – bool

**Optional But Highly Recommended**

* `gaid`– Google Advertising ID is a unique, randomly generated identifier assigned by Google to Android devices, which enables advertisers to track and measure users’ advertising interactions across apps and services.&#x20;
* `idfa`– Identifier for Advertisers is a unique, randomly generated code that Apple assigns to each user’s device, allowing advertisers to monitor and analyze advertising-related activity.
* `gender` – `m`, `f`, or `o`
* `age` – user age (numeric 0-100)
* `os_version` – version of the os

#### **Final URL Example for iOS users:**

```
https://sdk.mychips.io/content?content_id={your_adunit_id]&user_id={your_user_id}&idfa={your_user_idfa}&age={your_user_age}&gender={your_user_gender}&webview=1
```

**Example final URL with values replaced for iOS users:**

```
https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&idfa=AEBE52E7-03EE-455A-B3C4-E57283966239&age=27&gender=f&webview=1
```

#### **Final URL Example for** ANDROID **users:**

```
https://sdk.mychips.io/content?content_id={your_adunit_id]&user_id={your_user_id}&gaid={your_user_gaid}&age={your_user_age}&gender={your_user_gender}&webview=1
```

**Example final URL with values replaced for ANDROID users:**

```
https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&gaid=9b2f0c84-17a3-4f19-9c51-3e8b0a7c3c91&age=27&gender=f&webview=1
```

## Best Practice WebView

These **must** be followed when integrating the offerwall via WebView or a direct link. Failure to implement them may result in **broken reward flows**, **missing tracking**, or **bad UX**.

| ✅ Rule                                                                                                            | ❓ Why It Matters                                                                                                                     | 🛠 What to Do                                                                                                                                                                                   |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content_id` & `user_id` &`webview=1` are **mandatory**                                                           | Without them, users can't be identified and rewards can't be tracked                                                                 | Append both in the URL: `?content_id=...&user_id=...`                                                                                                                                           |
| `gaid` &`idfa` are **highly recommended**.                                                                        | We highly recommend including the **GAID** or **IDFA** parameter, depending on the platform, as this helps improve eCPM performance. | <p></p><p>Append the parameter to the URL depending on the platform:</p><ul><li><strong>iOS:</strong> <code>?idfa=...</code></li><li><strong>Android:</strong> <code>?gaid=...</code></li></ul> |
| Enable **JavaScript** in WebView                                                                                  | Required for rendering and logic of the offerwall                                                                                    | `webView.getSettings().setJavaScriptEnabled(true);`                                                                                                                                             |
| Enable **DOM Storage**                                                                                            | Enables session/localStorage — often used by the offerwall                                                                           | `webView.getSettings().setDomStorageEnabled(true);`                                                                                                                                             |
| Disable **WebView cache**                                                                                         | Prevents bugs from loading outdated or broken content                                                                                | `webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);`                                                                                                                                |
| Open URLs that start with `https://api.mychips.io` **and whose path contains `redirect`** in an external browser. | These are used for conversion tracking and if not well implemented could affect trackability                                         | Open External Browser                                                                                                                                                                           |
| Handle `mychips://` URL scheme                                                                                    | Bridge used by offerwall to trigger native logic (e.g. reward sync)                                                                  | override and ignore this schema                                                                                                                                                                 |
| Add a **custom error page** on failure                                                                            | Prevents user from seeing a blank screen when offline or error occurs                                                                | Override `onReceivedError()` and show fallback HTML content                                                                                                                                     |
| Implement proper **back navigation logic**                                                                        | Prevents broken UX and enables going back within the WebView                                                                         | close activity on back press when url contains "**home"**                                                                                                                                       |
| **Support file uploads in WebView**                                                                               | support require file/image upload/                                                                                                   | Use `WebChromeClient.onShowFileChooser()` and route result back via `onActivityResult()`                                                                                                        |
| **Do not preload**                                                                                                | preloading will affect impression tracking and impact eCPM negatively                                                                | <p><br></p>                                                                                                                                                                                     |

## Code Example

Below are sample codes for different languages.&#x20;

**Note:** Replace the values of the Offerwall URL parameters with your actual data. The values used in the code example are for demonstration purposes only.

{% tabs %}
{% tab title="Android (Java)" %}

```java
// Initialize the WebView and configure it
WebView webView = findViewById(R.id.webview);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // External link handling
        if (url.startsWith("https://api.mychips.io") && url.contains("redirect")) {
            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(intent);
            return true;
        }
        // Custom scheme handling
        if(url.startsWith("mychips://")) {
            // Trigger native logic here (e.g., reward synchronization)
            return true;
        }
        return false;
    }
    
    @Override
    public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
        // Load error page
        view.loadData("<html><body><h3>Failed to load. Please try again later.</h3></body></html>", "text/html", "UTF-8");
    }
});

String url = "https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&gaid=9b2f0c84-17a3-4f19-9c51-3e8b0a7c3c91&age=27&gender=f&webview=1";
webView.loadUrl(url);

```

{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
// Initialize the WebView and configure it
val webView = findViewById<WebView>(R.id.webview)
webView.settings.apply {
    javaScriptEnabled = true
    domStorageEnabled = true
    cacheMode = WebSettings.LOAD_NO_CACHE
}

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
        url?.let {
          if (it.startsWith("https://api.mychips.io") && it.contains("redirect")) {
                startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(it)))
                return true
            }
          if(it.startsWith("mychips://")){
                // Handle native logic here, e.g., reward synchronization
                return true
            }
        }
        return false
    }
    
    override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
        view?.loadData("<html><body><h3>Failed to load. Please try again later.</h3></body></html>", "text/html", "UTF-8")
    }
}

val url = "https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&gaid=9b2f0c84-17a3-4f19-9c51-3e8b0a7c3c91&age=27&gender=f&webview=1"
webView.loadUrl(url)

```

{% endtab %}

{% tab title="Android(Dart)" %}

<pre class="language-dart"><code class="lang-dart">import 'dart:io';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:url_launcher/url_launcher.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Offerwall Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const OfferwallPage(),
    );
  }
}

class OfferwallPage extends StatefulWidget {
  const OfferwallPage({super.key});
  @override
  State&#x3C;OfferwallPage> createState() => _OfferwallPageState();
}

class _OfferwallPageState extends State&#x3C;OfferwallPage> {
  late final WebViewController _controller;

  final String offerwallUrl = Uri.https(
    'sdk.mychips.io',
    '/content',
    {
      'content_id': '28617c7e-0178-4b89-b258-74bcf171e',
      'user_id': '5d41402abc4b2a76b9719d911017c592',
      'gaid': '9b2f0c84-17a3-4f19-9c51-3e8b0a7c3c91',
      'age': '27',
      'gender': 'f',
      'webview': '1',
    },
  ).toString();

  bool _isLoading = true;

  @override
  void initState() {
    super.initState();

    _controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..clearCache()
      ..clearLocalStorage()
      ..setBackgroundColor(const Color(0x00000000))
      ..setNavigationDelegate(
        NavigationDelegate(
          onPageStarted: (String url) {
            setState(() => _isLoading = true);
          },
          onPageFinished: (String url) {
            setState(() => _isLoading = false);
          },
          onNavigationRequest: (NavigationRequest request) async {
            final url = request.url;

            
            if (url.startsWith('mychips://')) {
              _syncRewardFromUri(url);
              return NavigationDecision.prevent;
            }

            
           if ((url.startsWith('https://api.mychips.io') &#x26;&#x26; url.contains('redirect'))) {
              final uri = Uri.parse(url);
              if (await canLaunchUrl(uri)) {
                  await launchUrl(uri, mode: LaunchMode.externalApplication);
              }
              return NavigationDecision.prevent;
<strong>            }
</strong>
            return NavigationDecision.navigate;
          },
          onWebResourceError: (WebResourceError error) {
            _controller.loadHtmlString(
              '&#x3C;html>&#x3C;body>&#x3C;h3>Failed to load. Please try again later.&#x3C;/h3>&#x3C;/body>&#x3C;/html>',
            );
          },
        ),
      )
      ..loadRequest(Uri.parse(offerwallUrl));
  }

  void _syncRewardFromUri(String uriString) {
    final uri = Uri.parse(uriString);
    final amount = uri.queryParameters['amount'];
    final currency = uri.queryParameters['currency'];

    if (amount != null &#x26;&#x26; currency != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('You received $amount $currency')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final currentUrl = await _controller.currentUrl() ?? '';
        if (currentUrl.contains('/home')) {
          return true;
        }
        if (await _controller.canGoBack()) {
          _controller.goBack();
          return false;
        }
        return true;
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Offerwall'),
          leading: IconButton(
            icon: const Icon(Icons.arrow_back),
            onPressed: () async {
              if (await _controller.canGoBack()) {
                _controller.goBack();
              } else {
                Navigator.of(context).pop();
              }
            },
          ),
        ),
        body: Stack(
          children: [
            WebViewWidget(controller: _controller),
            if (_isLoading)
              const Center(child: CircularProgressIndicator()),
          ],
        ),
      ),
    );
  }
}


</code></pre>

{% endtab %}

{% tab title="IOS(Swift)" %}

```swift
import UIKit
import WebKit

class OfferwallViewController: UIViewController, WKNavigationDelegate {
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.preferences.javaScriptEnabled = true
        // Use non-persistent data storage to disable caching
        webConfiguration.websiteDataStore = .nonPersistent()
        
        webView = WKWebView(frame: self.view.frame, configuration: webConfiguration)
        webView.navigationDelegate = self
        self.view.addSubview(webView)
        
        if let url = URL(string: "https://sdk.mychips.io/content?content_id=28617c7e-0178-4b89-b258-74bcf171e&user_id=5d41402abc4b2a76b9719d911017c592&idfa=AEBE52E7-03EE-455A-B3C4-E57283966239&age=27&gender=f&webview=1") {
            let request = URLRequest(url: url)
            webView.load(request)
        }
    }
    
    // Display an error page if loading fails
    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        let errorHTML = "<html><body><h3>Failed to load. Please try again later.</h3></body></html>"
        webView.loadHTMLString(errorHTML, baseURL: nil)
    }
    
    // Handle external links and custom URL schemes
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
                 decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if let urlString = navigationAction.request.url?.absoluteString {
            if urlString.hasPrefix("https://api.mychips.io") && urlString.contains("redirect") {

                if let redirectUrl = URL(string: urlString) {
                    UIApplication.shared.open(redirectUrl)
                    decisionHandler(.cancel)
                    return
                }
            }

            if urlString.hasPrefix("mychips://") {
                // Handle native logic here, e.g., reward synchronization
                decisionHandler(.cancel)
                return
            }
        }
        decisionHandler(.allow)
    }
}

```

{% endtab %}
{% endtabs %}


# Revenue API

## **Overview**

The **RevenueMetrics API** allows you to retrieve performance data for websites and ad units over a specific date range. This data includes key metrics such as revenue, impressions, clicks, and other performance indicators, providing actionable insights for optimizing ad performance.

***

## **API** Detail&#x73;**：**

### &#x20; **API Endpoint**

* <https://public-api.myappfree.com/graphql/index.html>

### &#x20;**Authentication**

Access to the API requires an API key. This key can be obtained in the [Universal Developer Portal](https://dashboard.maf.ad). Once obtained, the key must be included in the HTTP request header under the field `x-api-key`.&#x20;

* Auth Type:  API Key
* Header Name:  x-api-key
* Usage Example:

```graphql
x-api-key: YOUR_API_KEY
```

Replace the string **`YOUR_API_KEY`** in the code with your actual API key

### **Request Parameters**

| Parameter | Type   | Required | Description                              |
| --------- | ------ | -------- | ---------------------------------------- |
| dateFrom  | string | ✅ Yes    | Start date (format: YYYY-MM-DD HH:MM:SS) |
| dateTo    | string | ✅ Yes    | End date (format: YYYY-MM-DD HH:MM:SS)   |

<br>

## **GraphQL Query Example**

The following GraphQL query retrieves performance metrics for websites and ad units within a specified date range.&#x20;

**Request Example:**

```graphql
query RevenueMetrics {
  revenueMetrics(
    where: {
      dateFrom: "2024-12-31 00:00:00",
      dateTo: "2024-12-31 23:59:59"
    }
  ) {
    total
    items {
      siteId
      siteName
      adunitId
      adunitName
      impressions
      clicks
      revenue
      ecpm
      arpdau
      dau
    }
  }
}
```

\
Below are practical examples of how to send requests to the Revenue Metrics API using various popular programming languages, such as Node.js, Python, and C#. These examples demonstrate how to structure your requests, authenticate using an API key, and handle responses effectively.

{% tabs %}
{% tab title="Node.js" %}

```javascript
const fetch = require('node-fetch');

async function FetchRevenueMetrics(apiKey, dateFrom, dateTo) {
  const url = "https://public-api.myappfree.com/graphql/index.html";
  const headers = {
    "x-api-key": apiKey,
    "Content-Type": "application/json"
  };
  const query = {
    query: `
    query RevenueMetrics($fromDate: String!, $toDate: String!) {
      revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
        total
        items {
          siteId
          siteName
          adunitId
          adunitName
          impressions
          clicks
          revenue
          ecpm
          arpdau
          dau
        }
      }
    }`,
    variables: {
      fromDate: dateFrom,
      toDate: dateTo
    }
  };

  const response = await fetch(url, {
    method: "POST",
    headers: headers,
    body: JSON.stringify(query)
  });

  if (response.ok) {
    const data = await response.json();
    return data;
  } else {
    throw new Error(`Query failed with status ${response.status}: ${await response.text()}`);
  }
}

// Usage example
const apiKey = "YOUR_API_KEY";
const dateFrom = "2025-01-10 00:00:00";
const dateTo = "2025-01-10 10:00:00";

FetchRevenueMetrics(apiKey, dateFrom, dateTo)
  .then(data => {
    console.log("Revenue Metrics:");
    console.log(JSON.stringify(data, null, 2)); // Pretty print the response with indentation
  })
  .catch(err => console.error("Error:", err));

```

{% endtab %}

{% tab title="Python" %}

```python
import requests
import json

def FetchRevenueMetrics(api_key, date_from, date_to):
    url = "https://public-api.myappfree.com/graphql/index.html"
    headers = {
        "x-api-key": api_key,
        "Content-Type": "application/json"
    }
    query = {
        "query": """
        query RevenueMetrics($fromDate: String!, $toDate: String!) {
            revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
                total
                items {
                    siteId
                    siteName
                    adunitId
                    adunitName
                    impressions
                    clicks
                    revenue
                    ecpm
                    arpdau
                    dau
                }
            }
        }
        """,
        "variables": {
            "fromDate": date_from,
            "toDate": date_to
        }
    }

    response = requests.post(url, json=query, headers=headers)

    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Query failed with status code {response.status_code}: {response.text}")

# Usage example
api_key = "YOUR_API_KEY"
date_from = "2025-01-10 00:00:00"
date_to = "2025-01-10 10:00:00"

try:
    data = FetchRevenueMetrics(api_key, date_from, date_to)
    print("Revenue Metrics:")
    print(json.dumps(data, indent=2))  # Pretty print the response
except Exception as e:
    print("Error:", e)
```

{% endtab %}

{% tab title="C#" %}

```csharp
using System;
using RestSharp;
using Newtonsoft.Json;

class Program
{
    static dynamic FetchRevenueMetrics(string apiKey, string dateFrom, string dateTo)
    {
        var client = new RestClient("https://public-api.myappfree.com/graphql/index.html");
        var request = new RestRequest(Method.POST);
        request.AddHeader("Content-Type", "application/json");
        request.AddHeader("x-api-key", apiKey);

        var query = new
        {
            query = @"
            query RevenueMetrics($fromDate: String!, $toDate: String!) {
                revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
                    total
                    items {
                        siteId
                        siteName
                        adunitId
                        adunitName
                        impressions
                        clicks
                        revenue
                        ecpm
                        arpdau
                        dau
                    }
                }
            }",
            variables = new
            {
                fromDate = dateFrom,
                toDate = dateTo
            }
        };

        string jsonBody = JsonConvert.SerializeObject(query);
        request.AddParameter("application/json", jsonBody, ParameterType.RequestBody);

        IRestResponse response = client.Execute(request);

        if (response.IsSuccessful)
        {
            return JsonConvert.DeserializeObject(response.Content);
        }
        else
        {
            throw new Exception($"Query failed with status code {response.StatusCode}: {response.Content}");
        }
    }
    
    // Usage example
    static void Main(string[] args)
    {
        string apiKey = "YOUR_API_KEY";
        string dateFrom = "2025-01-10 00:00:00";
        string dateTo = "2025-01-10 10:00:00";

        try
        {
            var data = FetchRevenueMetrics(apiKey, dateFrom, dateTo);
            Console.WriteLine("Revenue Metrics:");
            Console.WriteLine(JsonConvert.SerializeObject(data, Formatting.Indented)); // Pretty print the response
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
    
    
}

```

{% endtab %}

{% tab title="Shell" %}

<pre class="language-sh"><code class="lang-sh"><strong>
</strong><strong>#!/bin/bash
</strong>
# Define API key, dates, and endpoint
API_KEY="YOUR_API_KEY"
DATE_FROM="2025-01-10 00:00:00"
DATE_TO="2025-01-10 10:00:00"
URL="https://public-api.myappfree.com/graphql/index.html"

# Define GraphQL query
QUERY=$(cat &#x3C;&#x3C;EOF
{
  "query": "query RevenueMetrics(\$fromDate: String!, \$toDate: String!) {
    revenueMetrics(where: { dateFrom: \$fromDate, dateTo: \$toDate }) {
      total
      items {
        siteId
        siteName
        adunitId
        adunitName
        impressions
        clicks
        revenue
        ecpm
        arpdau
        dau
      }
    }
  }",
  "variables": {
    "fromDate": "$DATE_FROM",
    "toDate": "$DATE_TO"
  }
}
EOF
)

# Send POST request using curl
RESPONSE=$(curl --silent --show-error --fail --request POST \
  --url "$URL" \
  --header "Content-Type: application/json" \
  --header "x-api-key: $API_KEY" \
  --data "$QUERY")

# Check if the response is successful
if [ $? -eq 0 ]; then
  echo "Revenue Metrics:"
  echo "$RESPONSE" | jq .  # Pretty print JSON using jq
else
  echo "Error: Failed to fetch revenue metrics" >&#x26;2
  exit 1
fi

</code></pre>

{% endtab %}
{% endtabs %}

## **Response Example**

**Successful Response (200 OK)**

If your request is successful, the response will follow this JSON structure:

```graphql
{
  "data": {
    "revenueMetrics": {
      "total": 1,
      "items": [
        {
          "siteId": 123,
          "siteName": "Example Site",
          "adunitId": 456,
          "adunitName": "Top Banner",
          "impressions": 10000,
          "clicks": 500,
          "revenue": 1500.50,
          "ecpm": 15.05,
          "arpdau": 3.25,
          "dau": 500
        }
      ]
    }
  }
}
```

## **Key Response Fields**

Below is a table summarizing key fields returned in the API responses. This will help you interpret and use the data effectively in your application.

| Field       | Type    | Description                           |
| ----------- | ------- | ------------------------------------- |
| total       | number  | Total item returned                   |
| siteId      | integer | Unique identifier for the site        |
| siteName    | string  | Name of the site                      |
| adunitId    | integer | Unique identifier for the ad unit     |
| adunitName  | string  | Name of the ad unit                   |
| impressions | integer | Total ad impressions                  |
| clicks      | integer | Total ad clicks                       |
| revenue     | number  | Total revenue generated               |
| ecpm        | number  | Effective cost per mille              |
| arpdau      | number  | Average revenue per daily active user |
| dau         | integer | Daily active users                    |

***

## **Error Response Examples**

When interacting with the API, you may encounter errors if the request is not properly formatted or authorized. This section highlights common issues, their causes, and how to resolve them. Below are examples of typical error responses and what they indicate.

**1. Invalid Date Range**:

```graphql
{
  "errors": [
    {
      "message": "Property DateFrom must be earlier than or equal to DateTo. Please correct the date range.",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "revenueMetrics"
      ]
    }
  ],
  "data": {
    "revenueMetrics": null
  }
}
```

**2. Syntax or Time Range Error:**

```graphql
{
    "errors": [
        {
            "message": "Property DateTo should be in the format yyyy-MM-dd HH:mm:ss. Please correct it.",
            "locations": [
                {
                    "line": 2,
                    "column": 5
                }
            ],
            "path": [
                "revenueMetrics"
            ]
        }
    ],
    "data": {
        "revenueMetrics": null
    }
}
```

**3. Invalid API Key:**

```graphql
{
    "errors": [
        {
            "message": "The current user is not authorized to access this resource.",
            "locations": [
                {
                    "line": 2,
                    "column": 5
                }
            ],
            "path": [
                "revenueMetrics"
            ],
            "extensions": {
                "code": "AUTH_NOT_AUTHORIZED"
            }
        }
    ],
    "data": {
        "revenueMetrics": null
    }
}
```


# Sample Revenue Metrics API Requests

Below are practical examples of how to send requests to the RevenueMetrics API using various popular programming languages, such as Node.js, Python, and C#. These examples demonstrate how to structure your requests, authenticate using an API key, and handle responses effectively.

## &#x20;Node.js Example

```javascript
const fetch = require('node-fetch');

async function fetchRevenueMetrics(apiKey, dateFrom, dateTo) {
  const url = "https://universal-api.myappfree.com/graphql/index.html";
  const headers = {
    "x-api-key": apiKey,
    "Content-Type": "application/json"
  };
  const query = {
    query: `
    query RevenueMetrics($fromDate: String!, $toDate: String!) {
      revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
        total
        items {
          siteId
          siteName
          adunitId
          adunitName
          impressions
          clicks
          revenue
          ecpm
          arpdau
          dau
        }
      }
    }`,
    variables: {
      fromDate: dateFrom,
      toDate: dateTo
    }
  };

  const response = await fetch(url, {
    method: "POST",
    headers: headers,
    body: JSON.stringify(query)
  });

  if (response.ok) {
    const data = await response.json();
    return data;
  } else {
    throw new Error(`Query failed with status ${response.status}: ${await response.text()}`);
  }
}

// Usage example
const apiKey = "YOUR_API_KEY";
const dateFrom = "2025-01-10 00:00:00";
const dateTo = "2025-01-10 10:00:00";

fetchRevenueMetrics(apiKey, dateFrom, dateTo)
  .then(data => {
    console.log("Revenue Metrics:");
    console.log(JSON.stringify(data, null, 2)); // Pretty print the response with indentation
  })
  .catch(err => console.error("Error:", err));
```

## Python Example

```python
import requests
import json

def fetch_revenue_metrics(api_key, date_from, date_to):
    url = "https://universal-api.myappfree.com/graphql/index.html"
    headers = {
        "x-api-key": api_key,
        "Content-Type": "application/json"
    }
    query = {
        "query": """
        query RevenueMetrics($fromDate: String!, $toDate: String!) {
            revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
                total
                items {
                    siteId
                    siteName
                    adunitId
                    adunitName
                    impressions
                    clicks
                    revenue
                    ecpm
                    arpdau
                    dau
                }
            }
        }
        """,
        "variables": {
            "fromDate": date_from,
            "toDate": date_to
        }
    }

    response = requests.post(url, json=query, headers=headers)

    if response.status_code == 200:
        return response.json()
    else:
        raise Exception(f"Query failed with status code {response.status_code}: {response.text}")

# Usage example
api_key = "YOUR_API_KEY"
date_from = "2025-01-10 00:00:00"
date_to = "2025-01-10 10:00:00"

try:
    data = fetch_revenue_metrics(api_key, date_from, date_to)
    print("Revenue Metrics:")
    print(json.dumps(data, indent=2))  # Pretty print the response
except Exception as e:
    print("Error:", e)
```

## C# Example

```csharp
var client = new RestClient("https://universal-api.myappfree.com/graphql/index.html");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddHeader("x-api-key", "YOUR_API_KEY");
request.AddParameter("application/json", @"{
  ""query"": ""query RevenueMetrics($fromDate: String!, $toDate: String!) {
    revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {
      total
      items {
        siteId
        siteName
        adunitId
        adunitName
        impressions
        clicks
        revenue
        ecpm
        arpdau
        dau
      }
    }
  }"",
  ""variables"": {
    ""fromDate"": ""2025-01-10 00:00:00"",
    ""toDate"": ""2025-01-10 10:00:00""
  }
}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

## Shell Example

```sh
curl --request POST \
  --url 'https://universal-api.myappfree.com/graphql/index.html' \
  --header 'Content-Type: application/json' \
  --header 'x-api-key: YOUR_API_KEY' \
  --data '{
    "query": "query RevenueMetrics($fromDate: String!, $toDate: String!) {\n      revenueMetrics(where: { dateFrom: $fromDate, dateTo: $toDate }) {\n        total\n        items {\n          siteId\n          siteName\n          adunitId\n          adunitName\n          impressions\n          clicks\n          revenue\n          ecpm\n          arpdau\n          dau\n        }\n      }\n    }",
    "variables": {
      "fromDate": "2025-01-10 00:00:00",
      "toDate": "2025-01-10 10:00:00"
    }
  }'

```


# API


# License

### MIT License

Copyright (c) 2025 MyChips ([Maf.ad](http://maf.ad/)) and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy\
of this software and associated documentation files (the "Software"), to deal\
in the Software without restriction, including without limitation the rights\
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\
copies of the Software, and to permit persons to whom the Software is\
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all\
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

***

This repository and the following SDKs are licensed under the MIT License (see LICENSE):

\- mychips-react-sdk\
\- mychips-react-native-sdk\
\- mychips-android-sdk\
\- mychips-ios-sdk\
\- mychips-flutter-sdk\
\- mychips-unity-sdk


