# LLM NFT Checkout

````
# NFT Checkout Documentation

---

## Table of Contents

* [Initialize the Widget](#initialize-the-widget)
* [Options](#options)
  * [General Options](#general-options)
  * [Smart Contract Options](#smart-contract-options)
  * [Pre-filled User Data](#pre-filled-user-data)
  * [Widget Customization](#widget-customization)
* [Create the Signed Data Object](#create-the-signed-data-object)
  * [Signed Data Generation Example](#signed-data-generation-example)
  * [Private Key](#private-key)
* [The `extra` Object](#the-extra-object)
  * [Screens Displaying Item Information](#screens-displaying-item-information)
* [Simplifying the UI](#simplifying-the-ui)
* [Limiting Fiat Currencies](#limiting-fiat-currencies)
* [Data Prefill](#data-prefill)
* [Listeners Object](#listeners-object)
* [Widget Display Methods](#widget-display-methods)
* [Converter Endpoint Specification](#converter-endpoint-specification)
  * [Rate Limit](#rate-limit)
  * [Parameters](#parameters)
  * [Sample Requests](#sample-requests)
  * [Response & Error Codes](#response--error-codes)
* [Verifying Your Smart Contract](#verifying-your-smart-contract)
* [Generating a Keypair](#generating-a-keypair)
* [Error Codes & Troubleshooting](#error-codes--troubleshooting)
* [Supported Coins and Blockchains](#supported-coins-and-blockchains)
  * [Production](#production)
  * [Sandbox](#sandbox)
  * [Test Token Contracts](#test-token-contracts)

---

## Initialize the Widget

> ❗️ **Mandatory Guidelines**
>
> To ensure the security and stability of your integration, please adhere to the following mandatory guidelines:
>
> * **Official SDK Only:** Using our [official SDK](https://www.npmjs.com/package/@wert-io/widget-initializer) is the **ONLY** supported method for initializing the widget.
> * **React Projects:** If you are building with React, please use our [dedicated wrapper](https://www.npmjs.com/package/@wert-io/module-react-component).
> * **Mobile Integration:** For mobile applications, create a web app utilizing our [SDK](https://www.npmjs.com/package/@wert-io/widget-initializer) and embed the web app within your native app using a WebView or equivalent technology.
> * **No Direct Redirects:** Opening the widget via a direct redirect link is not supported.
>
> 💡 **Need a head start?** Check out our [GitHub Integration Examples](https://github.com/wert-io/widget-integration-example/blob/master/src/buy-nft.ts) for reference implementations.

> 💡 **Did you know?**
>
> Our NFT checkout can also be used for any smart contract interaction such as facilitating token swaps for DeFi projects. Basically anything you could do from your own wallet, we can do from ours but on behalf of the user paying with fiat, and the best part, the end user is still the recipient of the transaction! Check out some great examples here at our [Forming input data](https://docs.wert.io/docs/forming-input-data) page.

Once the SDK is installed, you can use the following code snippet to initialize the payment module:

```javascript
// Import the SDK
import WertWidget from "@wert-io/widget-initializer";

// Initialize the payment module
const wertWidget = new WertWidget({
  // Configuration options
  // ...
});

// Further code to handle widget events and interactions
```

---

## Options

Here are the options you can pass to the widget.

### General Options

| Property | Required | Description | Data Type | Default value | Possible values |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `partner_id` | Yes | Provided when you register as a partner. | `string` | - | - |
| `origin` | In sandbox | Initializes the module in a sandbox/production environment. | `string` | `https://widget.wert.io` | `https://sandbox.wert.io` |
| `click_id` | No | Unique identifier for the order, which helps you track it. | `string` | - | `uuid_v4()` |
| `currencies` | No | Set currencies to control what appears in the widget; if empty, invalid, or not passed, all supported currencies are shown, and in simple_full_restrict sessions a mismatched currency will result in a ‘Purchase can’t be made’ message. Example: `JSON.stringify(['EUR'])` | `string` | - | `(['USD', 'EUR'])` |
| `redirect_url` | No | It includes a button *'Back to {Partner Name}'* on the widget (not available in iFrame mode). It also redirects users from KYC emails to proceed with payment. | URL `string` (with protocol) | - | `https://origin.us/item_id` |
| `support_url` | No | Full url string (with protocol). This link will be used for helping users with failed orders. | URL `string` (with protocol) | - | `https://partner.com/support` |
| `listeners` | No | You can use this if you want to listen to some module events and react to them. | `object` | - | See [listeners object](#listeners-object) |
| `payment_method` | No | If set, this method will be pre-selected and shown first in the list of available methods. Other methods will still be available unless `payment_method_restriction` is used. | `string` | `card` | `card`, `google-pay`, `apple-pay`, `sepa`, `cash-app` |
| `payment_method_restriction` | No | If true, the widget will show only the method specified in `payment_method` (if it’s available). If that method isn’t available, the widget will fall back to showing all available methods. | `boolean` | `false` | `true`, `false` |
| `hide_fee_breakdown` | No | Hides fee details for all payment methods and only the fee value is shown. | `boolean` | `false` | - |

### Smart Contract Options

| Property | Required | Description | Data Type | Default value | Possible values |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `sc_address` | Yes | The address of the smart contract. | `string` | - | - |
| `sc_input_data` | Yes | Input data that will be used for the smart contract execution (in hex format). For Tezos, this must be Michelson code passed as JSON transformed into hex format. | `string` | - | See [Forming input data](https://docs.wert.io/docs/forming-input-data) |
| `signature` | Yes | A digital signature encrypts the request, ensuring you have sent it. The private key stored on your backend signs the request, and the public key saved in our system decrypts it. | `string` | - | See [Generate signed data](#create-the-signed-data-object) |

### Pre-filled User Data

| Property | Required | Description | Data Type | Default value | Possible values |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `country_of_residence` | No | User’s country of residence. | `string` | - | [alpha2](https://www.nationsonline.org/oneworld/country_code_list.htm) code of the country |
| `state_of_residence` | No | User’s state of residence (for USA). | `string` | - | [alpha2](https://www.iso.org/obp/ui/#iso:code:3166:US) state |
| `date_of_birth` | No | User’s date of birth. | `string` | - | `DD/MM/YYYY`, `MM/DD/YYYY` (USA) |
| `email` | No | User’s email address. | `string` | - | `test@test.com` |
| `full_name` | No | The user’s first and last name. | `string` | - | min 3, max 69 letters; `RegExp(/(\w+\s)\w+/)` |
| `phone` | No | User’s phone number in international format (E.164 standard). The ‘+’ is optional. | `string` | - | `+11014321111` |
| `card_country_code` | No | Card billing address alpha2 country code | `string` | - | `US` |
| `card_city` | No | Card billing address city | `string` | - | `Los Angeles` |
| `card_state_code` | No | Card billing address alpha2 state code (For US) | `string` | - | `CA` |
| `card_post_code` | No | Card billing address postal code | `string` | - | `12345` |
| `card_street` | No | Card billing address street | `string` | - | `Main street 1` |

### Widget Customization

| Property | Required | Description | Data Type | Default value | Possible values |
| :--- | :--- | :--- | :--- | :--- | :--- |
| `lang` | No | Language of the widget. | `string` | `en` | `en`, `fr`, `ja`, `es`, `ko`, `zh`, `tr`, `de`, `ka`, `th` |
| `skip_init_navigation` | No | By default, the module suggests the nearest purchase route based on given parameters. Setting to ‘true’ skips this logic. | `boolean` | `undefined` | `true` |
| `theme` | No | Module’s theme color. | `string` | `undefined` | `dark` |
| `brand_color` | No | Custom brand color that affects primary buttons, tooltips, steppers, tabs, checkboxes, toasts, pie countdowns. | `string` | - | `#FF0000` |
| `is_crypto_hidden` | No | It hides crypto mentions and exchange rates. Please reach out to us to enable this feature for you. | `boolean` | `undefined` | `true` |

> **Supported Languages**: `en` - English, `fr` - French, `ja` - Japanese, `es` - Spanish, `ko` - Korean, `zh` - Chinese, `tr` - Turkish, `de` - German, `ka` - Georgian, `th` - Thai

#### Boolean Usage
Any value passed to a property with the boolean type is considered true. Example: `is_crypto_hidden: "test"` is equal to `is_crypto_hidden: true`.

---

## Create the Signed Data Object

> ⚠️ **Warning**
>
> If your Ethereum or Polygon smart contract can't be verified on Etherscan/Polygonscan, please check [Verifying your smart contract](#verifying-your-smart-contract) and return here afterward.

Use our [signature helper](https://www.npmjs.com/package/@wert-io/widget-sc-signer) to construct the signature, which must be passed to the widget.

Once the signature helper is installed, you can use the following code snippet:

```javascript
// Import the signature helper
import { signSmartContractData } from '@wert-io/widget-sc-signer';

// Create a configuration object with signed data
const signedData = signSmartContractData(options, privateKey);

// Pass the `signedData` object back to the widget along with the widget options
const wertWidget = new WertWidget({
    ...signedData,
    ...otherWidgetOptions,
});
```

Options to pass to the object:

| Property | Type | Description | Example |
| :--- | :--- | :--- | :--- |
| `address` | String | The user’s address that receives the token/NFT will also act as a fallback address if a smart contract can’t be executed. In case of fallback, we will transfer the `commodity_amount` to this address. | `0xbD25e3F08d1302b33fBD678CEF66323CC4190a24` |
| `commodity` | String | Cryptocurrency which should be sent to the smart contract, see [Supported coins and blockchains](#supported-coins-and-blockchains) | `commodity: 'ETH'` |
| `network` | String | Network for the cryptocurrency, see [Supported coins and blockchains](#supported-coins-and-blockchains) | `network: 'ethereum'` |
| `commodity_amount` | Number | Amount of cryptocurrency necessary for executing the given smart contract. The precision is 8 decimal places; please round up, or you will get a 9004 signature error code. | `0.03` (maximum 8 decimal places) |
| `sc_address` | String | The address of the smart contract. | `0x0000000000000000000000000000000000000002` |
| `sc_input_data` | String | Input data used for the smart contract execution in hex format. For Tezos, this must be Michelson code passed as JSON transformed into hex format. See [Forming input data](https://docs.wert.io/docs/forming-input-data). | `0x40c10f1900000000000000000000000085f584812fc19c69e4dd586f06df93aa7bdadd4d0000000000000000000000000000000000000000000000000016345785d8a0000` |

### Signed Data Generation Example

```javascript
import { signSmartContractData } from '@wert-io/widget-sc-signer';
import { v4 as uuidv4 } from 'uuid';
import { Buffer } from 'buffer/';

window.Buffer = Buffer; // needed to use `signSmartContractData` in browser

const signedData = signSmartContractData(
  {
    address: '0x96D5990185022212d367A0e09263B12Dbb4EE06A',
    commodity: 'ETH',
    network: 'ethereum',
    commodity_amount: 0.005,
    sc_address: '0x3b2305502bd6f8b1eb2ed474ac15c61c6702b18b',
    sc_input_data:
      '0x9dae76ea000000000000000000000000000000000000000000000000000000000000003700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001',
  },
  privateKey
);

const wertWidget = new WertWidget({
  partner_id: 'YOUR_PARTNER_ID',
  ...signedData,
});
```

You can find further examples on [GitHub](https://github.com/wert-io/widget-integration-example/tree/master).

If you cannot use our signature helper, follow the [Signing a payment request guide](https://docs.wert.io/docs/signing-a-payment-request) to manually create the signature.

### Private Key

* **For the sandbox** environment, Wert will provide you with the private key; if you don’t have it, please get in touch with us via [email or Discord](https://docs.wert.io/docs/integration-steps#1-get-your-sandbox-credentials).
* **For production**, you need to generate the key pair and provide the public key to Wert. Please refer to [Keypair creation](#generating-a-keypair).

> ⚠️ **Security Warning**
>
> To keep your private key safe, **you should construct the signature on your backend** and pass it to the frontend when initializing the widget.
>
> *Note: This is NOT a wallet private key, it is just a digital signature to encrypt the request.*

---

## The `extra` Object

The `extra` object is optional and can be used to show the details of your item.

![Extra Object UI Example](https://files.readme.io/1b9bf40-image.png)

Options:

| Property | Type | Description |
| :--- | :--- | :--- |
| `author_image_url` | String | The URL of the author's avatar |
| `author` | String | The name of the author |
| `image_url` | String | The URL of the item's image. Otherwise, the default image will be shown |
| `name` | String | The name of the item |
| `header` | String | Custom text at the top of the widget that is shown instead of `Buy {category}` |
| `category` | String | The category of the item. If `category` and `header` are not provided, the default value will be `Buy Token` |

Default parameters display:

![Default Parameters Display](https://files.readme.io/9e4a468-Untitled_7.png)

```javascript
const widget = new WertWidget({
  ...options,
  extra: {
    item_info: {
      author_image_url: "string", // Example: https://something.com/images/author_image.jpg
      author: "Item's Author",
      image_url: "string", // Example: https://something.com/images/image.jpg
      name: "Item's Name",
      category: "Item's Category",
      header: "Custom Header",
    },
  },
});
```

### Screens Displaying Item Information

*Note: Images larger than 1MB will not be shown in the following order screens.*

* **Order Details:** ![Order Details](https://files.readme.io/4b3cb85-Untitled_11.png)
* **Payment Error Screen:** ![Payment Error](https://files.readme.io/ede5b0a-Untitled_13.png)
* **Order History:** ![Order History](https://files.readme.io/d8f1e67-Untitled_9.png)

---

## Simplifying the UI

> ❗️ **Restriction**
>
> This feature is only available for the NFT Checkout with NFTs priced in USDC / USDT on Polygon, BSC, Base or USDtez on Tezos.

By default, the Widget shows all purchase information, including the cryptocurrency amount and exchange rate. This information is also available in the order details and order history:

* **Confirm your purchase screen:** ![Confirm your purchase](https://files.readme.io/0208cf3-Untitled_2.png)
* **Order details and Order history screens:** ![Order details and Order history](https://files.readme.io/8ae2810-Untitled_3.png)

However, if you prefer to display less information to your users, these UI elements can be hidden from the interface.

**Steps to enable:**
1. Contact Wert’s Support team to turn this feature on for you.
2. Pass `is_crypto_hidden: true` upon Widget initialization as in the example below:

```javascript
const widget = new WertWidget({
  partner_id: 'YOUR_PARTNER_ID',
  is_crypto_hidden: true,
  ...
});
```

**As a result:**
1. The Widget will only load fiat amounts in the ‘**You Pay**’ and ‘**Network fee**’ sections. 
2. ‘**Exchange rate**’ will not be shown.
3. Order details only contain fiat amounts and won’t display ‘**Exchange rate**’.
4. The ‘**Failed order**’ screen will only display the fiat amount.
5. Orders with `is_crypto_hidden: true` will display only fiat amounts in ‘**Order History**’. Orders without this parameter will be displayed with both amounts.
6. These UI elements will also be hidden from automatic emails that users get for successful orders and receipts that they manually request.

* **Confirm purchase & Order details (Simplified):**
  ![Confirm purchase simplified](https://files.readme.io/40ba2a6-Bildschirmfoto_2023-10-05_um_15.56.31_Kopie.png)
  ![Order details simplified](https://files.readme.io/f104dad-Bildschirmfoto_2023-10-05_um_15.59.22.png)
* **Failed order and Order history screens (Simplified):**
  ![Failed order simplified](https://files.readme.io/d412038-Untitled_4.png)

---

## Limiting Fiat Currencies

By default, the widget displays all available fiat currencies and highlights the preferred option automatically based on the user’s IP address.

![Default Currencies View](https://files.readme.io/fc3d69013a221b0d2e358d2fcd61e1edf3ea066ad797c1930a0bf47b0f6c1a98-image.png)

You can also choose to display only a single fiat currency — in this case, the drop-down list will be hidden.

Pass the following data to load the Widget with a single fiat currency:

```jsx
const wertWidget = new WertWidget({
  // ... other options ...
  currencies: JSON.stringify(['USD']),
});
```

![Single Currency View](https://files.readme.io/be823137cd397c52553ce3524af376e84b7597bc9e51f361caf86a44686b1e72-image.png)

---

## Data Prefill

To simplify the purchase flow for your users, you can pass some of their details directly to Wert upon widget initialization. You are always in control of how much data you pass. You can provide ‘Know Your Customer’ information for faster user verification or just a phone number.

Fields:

| Property | Data Type | Description | Possible values |
| :--- | :--- | :--- | :--- |
| `full_name` | `string` | The user’s first and last name. | Between 3 and 69 letters. `RegExp(/(\w+\s)\w+/)` |
| `date_of_birth` | `string` | User’s date of birth. | `DD/MM/YYYY`, `MM/DD/YYYY` (for USA) |
| `phone` | `string` | User’s phone number in international format (E.164 standard). The ‘+’ is optional. | `+11014321111` |
| `email` | `string` | User’s email address. | `test@test.com` |
| `country_of_residence` | `string` | User’s country of residence. | `alpha2` code of the country |
| `state_of_residence` | `string` | User’s state of residence (for USA). | `alpha2` code of USA state |

```javascript
const widget = new WertWidget({
  partner_id: "YOUR_PARTNER_ID",
  phone: "+494949030",
  email: "test@test.io",
  full_name: "Name Surname",
  date_of_birth: "31/07/1990",
  country_of_residence: "DE",
});
```

As a result, the user will not need to enter the data you provide to us manually, but they will always have the option to correct any information.

The Widget will load with pre-filled information on the following screens:

* **General Pre-fill Screens:** ![Enter phone number Card details Personal details](https://files.readme.io/e65102d-Bildschirmfoto_2023-11-30_um_14.31.11.png)
* **US Users Pre-fill Screens:** ![Left to right: 'Enter phone' screen, 'Card details' screen and 'Personal details' screen](https://files.readme.io/19f3db0-Untitled.png)

---

## Listeners Object

The listeners object helps you keep track of events in the module and respond accordingly.

Available events:

| Event | Expected data | Description |
| :--- | :--- | :--- |
| `close` | `undefined` | Triggered when the user closes a widget. |
| `error` | `{ name: String; message: String; }` | Widget error message (not payment-related), see [error codes](#error-codes--troubleshooting). |
| `loaded` | `undefined` | Indicates the module is loaded with necessary data and ready to receive extra data if needed. This event may duplicate if the user is redirected to their bank's 3-D Secure verification page. |
| `payment-status` | `{ status: String, payment_id: String, order_id: String, tx_id: String, external_order_id: String }` | Possible statuses: `pending`, `canceled`, `failed`, `success`, `failover`. Events may not be unique for each status. `tx_id` appears in the `pending` status. |
| `position` | `{ step: String }` | Notifies changes in user flow position. Step names: `account`, `account-history`, `account-card`, `account-address`, `home`, `wallet`, `login`, `onboarding`, `card`, `verification`, `verification-status`, `order-review`, `order-status`, `warranty`, `error`. |
| `rate-update` | `{ ticker: String, fee_percent: String, currency_amount: String, fee_amount: String, commodity_amount: String, purchase_amount: String, miner_fee: String, currency_miner_fee: String }` | Updates when the widget rate changes. We fix the exchange rate for 30 seconds. |

Add the listeners object like this: the key is the event type, and the value is your callback function.

```javascript
const widget = new WertWidget({
  ...options,
  listeners: {
    position: (data) => console.log("step:", data.step),
  },
});
```

To subscribe to the payment status data:

```javascript
const widget = new WertWidget({
  ...options,
  listeners: {
    'payment-status': data => console.log('Payment status:', data)
  },
});
```

*This will only be triggered when simulating the purchase, not in intermediary steps.*

---

## Widget Display Methods

After creating and configuring the widget, you can use a few methods to control the widget's display. Please make sure you use the latest version of [SDK](https://www.npmjs.com/package/@wert-io/widget-initializer) (7.x.x).

Widget class methods:

| Method | Description |
| :--- | :--- |
| `open` | Mount the widget in the DOM and make it visible (initializes the widget as an iFrame). |
| `close` | Close the widget modal. |
| `updateTheme` | Switch the widget's theme without reloading it. |
| `addEventListeners` | Add event listeners. See [listeners object](#listeners-object). |
| `removeEventListeners` | Remove event listeners. |

Usage examples:

```javascript
// Open widget as iFrame
wertWidget.open();

// Switch theme without reloading (call only after widget is fully loaded)
wertWidget.updateTheme({
  theme: 'dark', // Use 'undefined' for default light theme
  brand_color: 'red' // String, HEX, rgb, etc.
});

// Add event listeners
wertWidget.addEventListeners({
  position: data => console.log('step:', data.step),
});

// Remove event listeners
wertWidget.removeEventListeners('rate-update');
// or
wertWidget.removeEventListeners([ 'rate-update', 'payment-status' ]);
// or
wertWidget.removeEventListeners();
```

---

## Converter Endpoint Specification

The converter endpoint allows you to receive information on a transaction without going through the execution flow. You can use the response to display the exact amount before opening the widget. Additionally, the amount from response can be passed into the widget.

| Environment | URL | Method |
| :--- | :--- | :--- |
| `Sandbox` | `https://sandbox.wert.io/api/v3/partners/convert` | `POST` |
| `Production` | `https://widget.wert.io/api/v3/partners/convert` | `POST` |

**Headers:**
```http
X-Partner-ID: YOUR_PARTNER_ID
Content-Type: application/json
```

### Rate Limit

To ensure API stability and fair usage, we implement a rate limit on all conversion requests. If you exceed these limits, the API will return a `429 Too Many Requests` error.

* **Threshold**: 1 request per 250 ms per IP address.
* **Throughput**: Approximately 4 requests per second.
* **Lockout Period**: Any request sent within 250 ms of the previous request from the same IP will return `429 - Too Many Requests` error.

> 🚧 **Recommendation**
>
> To prevent users from hitting these limits, we recommend implementing the conversion logic or a request-throttler directly in your frontend.

### Parameters

> 🚧 **Note**: Only `USD` is available in sandbox.

| Property | Required | Type | Description |
| :--- | :--- | :--- | :--- |
| `from` | Yes | `string` | Base asset (Fiat or Crypto). Refer to [Supported coins and blockchains](#supported-coins-and-blockchains) |
| `network` | Yes | `string` | Base asset network. Refer to [Supported coins and blockchains](#supported-coins-and-blockchains) |
| `to` | Yes | `string` | Quote asset (Fiat or Crypto). Refer to [Supported coins and blockchains](#supported-coins-and-blockchains) |
| `amount` | Yes | `numerical` | Base asset amount |
| `partner_data` | Required for smart contract | `object` | Partner data object |
| `sc_address` | Required for smart contract | `string` | The address of your smart contract |
| `sc_input_data` | Required for smart contract | `string` | Input data that will be used for the smart contract execution in hex format |
| `signature` | Required for smart contract | `string` | A digital signature used to encrypt the request to ensure you have sent it |

### Sample Requests

#### Crypto Transaction
```bash
curl -X POST --url 'https://sandbox.wert.io/api/v3/partners/convert' -H 'Content-Type: application/json' -H 'X-Partner-ID: your-partner-id' -d '{
    "from": "ETH",
    "network": "sepolia",
    "to": "USD",
    "amount": 0.05
    }'
```

#### Smart Contract Transaction
```bash
curl -X POST --url 'https://sandbox.wert.io/api/v3/partners/convert' -H 'Content-Type: application/json' -H 'X-Partner-ID: your-partner-id' -d '{
    "from": "ETH",
    "network": "sepolia",
    "to": "USD",
    "amount": 0.05,
    "partner_data": {
      "sc_address": "0xC545CEae428785a5AE77bfF262600deC7F7d76d2",
      "sc_input_data": "0x9dae76ea000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000",
      "signature": "d035046e8eda9b2c9c4413bfd31ed42b7df056108c83110aa8ae70f9ca167e1a2047d1b906afa66a770939dd074d1eff27b029ec2c27b39dc30a076e31221209"
    }
}'
```

### Response & Error Codes

Response Parameters:

| Parameter | Description |
| :--- | :--- |
| `ticker` | The rate at which cryptocurrency is being sold. |
| `fee_percent` | % fee taken by the service. |
| `currency_amount` | The final amount in fiat that the user will pay. |
| `fee_amount` | Amount paid in fiat to cover the service fee. |
| `commodity_amount` | The amount of cryptocurrency that the user will receive. |
| `purchase_amount` | The amount of fiat that will be used to purchase cryptocurrency (`purchase_amount` = `currency_amount` - `fee_amount` - `currency_miner_fee`) |
| `miner_fee` | The amount of crypto that will be used to cover blockchain fees. |
| `currency_miner_fee` | Price of miner fee in fiat (`currency_miner_fee` = `miner_fee` • `ticker`). |

Sample 200 OK Response:
```json
{
    "status": "ok",
    "body": {
        "ticker": 2672.540034535,
        "fee_percent": 0.05,
        "currency_amount": 144.3,
        "fee_amount": 6.87,
        "commodity_amount": 0.05,
        "purchase_amount": 137.43,
        "miner_fee": 0.00142144,
        "currency_miner_fee": 3.8
    }
}
```

Sample 400 Bad Request Responses:
```json
{
    "status": "error",
    "error_description": "amount: must be no less than 0.",
    "request_id": "string"
}
```
```json
{
    "status": "error",
    "error_description": "amount is too low",
    "request_id": "string"
}
```
```json
{
    "status": "error",
    "error_description": "from: cannot be blank; to: cannot be blank.",
    "request_id": "string"
}
```
```json
{
    "code": 1302,
    "status": "error",
    "error_description": "unknown commodity",
    "request_id": "424f8c727a9b2664f2de61c16b5cf169"
}
```
```json
{
    "code": 1301,
    "status": "error",
    "error_description": "commodity is disabled",
    "request_id": "83c4bbaa60198284846fd77e0bb72293"
}
```
```json
{
    "code": 1013,
    "status": "error",
    "error_description": "unknown currency",
    "request_id": "701f7153101206e1e2ef871aa539f40b"
}
```
```json
{
    "code": 9001,
    "status": "error",
    "error_description": "the partner doesn't exist",
    "request_id": "76fec9b0ba9fda16c68a4ca2d038186d"
}
```

---

## Verifying Your Smart Contract

**EVM-compatible smart contracts** have to be verified on a block explorer. If your contract cannot be verified, you will need to add it to your Partner Dashboard by following these steps:

1. Login to your Partner Dashboard and locate `Settings -> Smart Contracts`.
2. Click `Add New Smart Contract`.
3. Add your smart contract address to `SC address` and the ABI of the implementation contract to `Contract ABI`.
   * *Note: If using a proxy contract, save the proxy address in `SC address` against the ABI of the implementation contract.*
4. Click `Add` to update the list in the Partner Dashboard.

After verification, you can interact with Wert as required. If you make any changes, you must add the new contract and ABI.

> 📘 **EVM Compatibility**
>
> Blockchains running the Ethereum Virtual Machine include: Ethereum, Polygon, Arbitrum, Binance, Base, Shibarium, Theta.
>
> **For contracts on other blockchains, verification isn't needed.**

---

## Generating a Keypair

> ⚠️ **Security Warning**
>
> To keep your private key safe, **you should construct the signature on your backend** and pass it to the frontend when initializing the widget.
>
> *Note: This is NOT a wallet private key, it is just a digital signature to encrypt the request.*

* **Sandbox**: Wert provides the private key (contact via email or Discord if missing).
* **Production**: You must generate a keypair and provide Wert with the public key to authenticate requests.

### Keypair Generation Code Example (JavaScript)

```javascript
import * as ed from '@noble/ed25519'; // ESM-only. Use bundler for common.js

(async () => {
  const privateKey = ed.utils.randomSecretKey(); // 32-byte Uint8Array or string
  const publicKey = await ed.getPublicKeyAsync(privateKey);

  console.log(`Private key: 0x${Buffer.from(privateKey).toString('hex')}`);
  console.log(`Public key: 0x${Buffer.from(publicKey).toString('hex')}`);
})();
```

Response Example:
```json
Private key: 0x44b8533802effb07e8910828454fd7c8a4fcb79bf95658b7ba9f259cec214d57
Public key: 0xc44f3e7f0b06ee8479e183e8ce36349f050b84ae1301642244fe1490a97661dc
```

**Steps:**
1. Install `@noble/ed25519` via `npm i @noble/ed25519`.
2. Generate random private key with `ed.utils.randomSecretKey()`.
3. Obtain public key via `ed.getPublicKeyAsync(privateKey)`.
4. Convert keypair to hex format and log to console. Share public key with Wert.

---

## Error Codes & Troubleshooting

### How to Locate Error Responses via DevTools
1. Go to the page with the error and right-click -> `Inspect`.
2. Select the `Network` tab.
3. Trigger the error or refresh.
4. Locate the red-highlighted request endpoint.
5. Inspect `Preview` and `Response` details.
6. Share Request ID, Payload, and Request URL when contacting support:
   ![Locating error details](https://files.readme.io/3a59d72-0216-ezgif.com-video-to-gif-converter.gif)

### Detailed Error Codes

#### Error 8001: Blockchain Execution Reverted
* **Endpoint**: `/convert`
* **Cause**: Error in contract/input data, or item already purchased.
* **Troubleshooting**:
  1. [Verify smart contract](#verifying-your-smart-contract). If using a proxy, save proxy address against implementation ABI.
  2. Check input data against ABI using EVM ABI decoder.
  3. Ensure both Hot Wallet and Estimation addresses are whitelisted if required.
  4. Ensure amount does not exceed decimal precision limits.
  5. Check contract for execution restrictions.
  6. Provide a successful transaction hash from a wallet executing the same input data to support.

#### Error 8002: Blockchain Invalid Input Data
* **Endpoint**: `/convert`
* **Cause**: Input variables or method name do not match before sending to node.
* **Troubleshooting**: Verify input data syntax, variables, and method names.

#### Error 8003: Blockchain Transaction Injection Error
* **Endpoint**: `/convert`
* **Cause**: Node error (e.g., `invalid method name`).
* **Troubleshooting**: Review node response description and verify Wert wallet balance.

#### Error 9001: Partner Does Not Exist
* **Endpoint**: `./partners/{partner_id}`
* **Cause**: Partner ID not found in database.
* **Troubleshooting**: Ensure correct `partner_id` for Sandbox vs Production environment.

#### Error 9004: Partner Data Not Verified By Signature
* **Endpoint**: `./validate-smart-contract`
* **Cause**: Signature does not match input data or wrong data was signed.
* **Troubleshooting**:
  1. Verify [signed parameters](#create-the-signed-data-object) and order.
  2. Ensure `commodity_amount` has at most 8 decimal places (round up).
  3. Check private/public key accuracy.

#### Error 9005: Partner Unverified Contract
* **Endpoint**: `./validate-smart-contract`
* **Cause**: Smart contract unverified on block explorer or Wert database.
* **Troubleshooting**: Verify on Etherscan/Polygonscan or follow the [verification guide](#verifying-your-smart-contract).

#### Error 9006: Partner Disabled SC
* **Endpoint**: `./validate-smart-contract`
* **Cause**: Smart Contracts disabled for Partner ID.
* **Troubleshooting**: Confirm environment (production/sandbox) or contact Wert support.

#### Error 1301, 1302: Commodity Unknown
* **Endpoint**: `/assets`, `/convert`, `/partner/convert`
* **Cause**: Commodity does not exist.
* **Troubleshooting**: Verify `commodity`, `network`, and `origin` match target environment.

#### Error 1304: Parent Commodity Disabled
* **Endpoint**: `/assets`
* **Cause**: Parent commodity disabled.
* **Troubleshooting**: Contact support to enable parent commodity.

#### Error 1305: Commodity Has No Available Pairs
* **Endpoint**: `/assets`
* **Cause**: Commodity enabled but lacks active trading pairs for partner.
* **Troubleshooting**: Select a different commodity with active pairs.

#### Error 1400: Contract Not Payable
* **Endpoint**: `./validate-smart-contract`
* **Cause**: Issue verifying smart contract.
* **Troubleshooting**: Check validity of `sc_address` and `address`; re-verify smart contract.

#### Error 1013: Invalid Field
* **Endpoint**: `./validate-smart-contract`
* **Cause**: Invalid data field.
* **Troubleshooting**:
  1. Update `commodity_amount` to meet minimum order requirements (~$1.50 Sandbox / ~$5.00 Production).
  2. Check `address` and `sc_address` data validity.

#### SDK Errors
* **Troubleshooting**: Update to the latest SDK version (`npm i @wert-io/widget-initializer@latest`).

#### Sandbox FKYC Issues
* **Troubleshooting**: Create a new sandbox account with a random phone number using OTP `0000`.

#### OTP / SMS Issues
* **Troubleshooting**:
  * In Sandbox, OTP is fixed to `0000`.
  * Check phone signal and carrier outage status.
  * Ensure mobile carrier is not blocking international SMS.
  * See full [OTP Troubleshooting guide](https://docs.wert.io/docs/going-live#%EF%B8%8F-otp--sms-troubleshooting).

---

## Supported Coins and Blockchains

### Production

| Cryptocurrency | Available networks | ‘commodity’ option | 'network' option | Supported for smart contracts | Decimal Precision |
| :--- | :--- | :--- | :--- | :--- | :--- |
| Aavegotchi | Polygon | `'GHST'` | `'polygon'` | Yes | 18 |
| Arbitrum | Arbitrum | `'ARB'` | `'arbitrum'` | Yes | 18 |
| AVAX | Avalanche | `'AVAX'` | `'avalanche'` | Yes | 18 |
| Binance Coin | Binance | `'BNB'` | `'bsc'` | Yes | 18 |
| Bitcoin | Bitcoin | `'BTC'` | `'bitcoin'` | No | 8 |
| Bone ShibaSwap | Shibarium | `'BONE'` | `'shibarium'` | Yes | 18 |
| Concordium | Concordium | `'CCD'` | `'concordium'` | Yes | 5 |
| cUSDT | Ethereum | `'CUSDT'` | `'mainnet'` | No | 6 |
| Ethereum | Ethereum, Arbitrum, Base, Hemi | `'ETH'` | `'ethereum'`, `'arbitrum'`, `'base'`, `'hemi'` | Yes | 18 |
| EUROe | Concordium | `'EUROE'` | `'mainnet'` | No | 6 |
| GPRO | Polygon | `'GPRO'` | `'polygon'` | Yes | 18 |
| Hemi | Hemi | `'HEMI'` | `'hemi'` | Yes | 18 |
| LayerAI | Ethereum, Binance | `'LAI'` | `'ethereum'`, `'bsc'` | No | 18 |
| LimeWire | Ethereum | `'LMWR'` | `'ethereum'` | Yes | 18 |
| MANA | Polygon, Ethereum | `'MANA'` | `'polygon'`, `'ethereum'` | Yes | 18 |
| NEAR | Near | `'NEAR'` | `'near'` | Yes | 8 |
| opBNB | opBNB | `'OPBNB'` | `'opbnb'` | Yes | 18 |
| POL | Polygon | `'POL'` | `'polygon'` | Yes | 18 |
| Rootstock BTC | Rootstock | `'RBTC'` | `'rootstock'` | Yes | 18 |
| Stacks | Stacks | `'STX'` | `'stacks'` | No | 6 |
| Solana | Solana | `'SOL'` | `'solana'` | Yes | 9 |
| Tezos | Tezos | `'XTZ'` | `'tezos'` | Yes | 6 |
| Tezos | Etherlink | `'XTZ'` | `'etherlink'` | Yes | 18 |
| TFUEL | Theta | `'TFUEL'` | `'theta'` | Yes | 18 |
| TON | The Open Network | `'TON'` | `'ton'` | Yes | 8 |
| USDC | Arbitrum, Base, Ethereum, Avalanche, Shibarium, Solana, Polygon, Hemi, Etherlink | `'USDC'` | `'arbitrum'`, `'base'`, `'ethereum'`, `'avalanche'`, `'shibarium'`, `'solana'`, `'polygon'`, `'hemi'`, `'etherlink'` | Yes | 6 |
| USDSM | Etherlink | `'USDSM'` | `'etherlink'` | Yes | 18 |
| USDT | Polygon, Ethereum, Binance, TON, Hemi | `'USDT'` | `'polygon'`, `'ethereum'`, `'bsc'`, `'ton'`, `'hemi'` | Yes | 6 (Polygon, Ethereum, TON, Hemi), 18 (BSC) |
| Vyvo | Vyvo | `'VSC'` | `'vyvo'` | Yes | 5 |
| Wrapped Ether | Polygon | `'WETH'` | `'polygon'` | Yes | 18 |
| XCAD | opBNB, Binance | `'XCAD'` | `'opbnb'`, `'bsc'` | Yes | 18 |

### Sandbox

| Cryptocurrency | Available networks | ‘commodity’ option | 'network' option | Decimal Precision |
| :--- | :--- | :--- | :--- | :--- |
| Binance | BSC | `'BNB'` | `'bsc'` | 18 |
| BONE | Puppynet | `'BONE'` | `'puppynet'` | 18 |
| Concordium | Concordium | `'CCD'` | `'concordium'` | 18 |
| cUSDC | Sepolia | `'cUSDC'` | `'sepolia'` | 6 |
| Ethereum | Sepolia, Base, Arbitrum | `'ETH'` | `'sepolia'`, `'base_sepolia'`, `'arbitrum_sepolia'` | 18 |
| EUROe | Concordium | `'EUROe'` | `'concordium'` | 18 |
| Hemi | Hemi | `'HEMI'` | `'hemi'` | 18 |
| POL | Amoy | `'POL'` | `'amoy'` | 18 |
| Solana | Solana | `'SOL'` | `'solana'` | 9 |
| Test Token | Amoy, BSC, KuCoin, Base, Arbitrum, Sepolia, opBNB, Puppynet, Hemi, Etherlink | `'TT'` | `'amoy'`, `'bsc'`, `'kucoin'`, `'base_sepolia'`, `'arbitrum_sepolia'`, `'sepolia'`, `'opbnb'`, `'puppynet'`, `'hemi'`, `'etherlink'` | 18 |
| Test Token | Solana | `'TT'` | `'solana'` | 9 |
| Tezos | Ghostnet, Etherlink | `'XTZ'` | `'ghostnet'`, `'etherlink'` | 6 |
| opBNB | opBNB | `'opBNB'` | `'opbnb'` | 18 |
| USDSM | Etherlink | `'usdsm'` | `'etherlink'` | 18 |

### Test Token Contracts

Test Token is the only ERC-20 token available in sandbox. It’s a standard ERC-20 token used to test the interaction of ERC-20 tokens against your smart contract. You can simulate a sandbox purchase to receive tokens for testing (no charge).

> ℹ️ **Deployed Addresses**:
>
> * **Arbitrum Sepolia network**: `0x103EF343661335B85BE98EfEfF7f726Be7CeeeDE`
> * **Ethereum Sepolia network**: `0x42D8BCf255125BB186459AF66bB74EEF8b8cC391`
> * **Polygon Amoy network**: `0x834bBE71a0a5C91A4Aff10Df400A963D95AD4775`
> * **Binance Smart Chain**: `0x4C07B79C3D8954A51Efc342EdA5D08f8b1f9ceC4`
> * **Base Sepolia network**: `0x3bE123Ff0ec7c0717D6C05C8957EA7880e2FfDcb`
> * **Puppynet**: `0x4C07B79C3D8954A51Efc342EdA5D08f8b1f9ceC4`
> * **Solana**: `Hza3mZYRv7ZZw2xo1kvWvD4YXLnhA47JtTZBhgr8LTU7`
> * **Hemi**: `0x33e8Bf90e1aafcE42490e05880810f6d09699e02`

## Frontend Integration Code Examples

### Widget Initialization MintNFT Example

```typescript
import WertWidget from '@wert-io/widget-initializer';
import type { Options } from '@wert-io/widget-initializer/types';
import { signSmartContractData } from '@wert-io/widget-sc-signer';
import { v4 as uuidv4 } from 'uuid';
import Web3 from 'web3';
import { Buffer } from 'buffer/';

window.Buffer = Buffer; // needed to use `signSmartContractData` in browser

/* We advise you not to use the private key on the frontend
   It is used only as an example
*/
if (window.ethereum) {
  (async () => {
    // Get user address
    const userAccounts = await window.ethereum.request({
      method: 'eth_requestAccounts',
    });

    const web3 = new Web3(window.ethereum);
    const userAddress = userAccounts[0];

    // Encode the call to mintNFT(address = userAddress, numberOfTokens = 1)
    const sc_input_data = web3.eth.abi.encodeFunctionCall(
      {
        inputs: [
          {
            internalType: 'address',
            name: 'to',
            type: 'address',
          },
          {
            internalType: 'uint256',
            name: 'numberOfTokens',
            type: 'uint256',
          },
        ],
        name: 'mintNFT',
        outputs: [],
        stateMutability: 'payable',
        type: 'function',
      },
      [userAddress, 1]
    );

    const privateKey =
      '0x57466afb5491ee372b3b30d82ef7e7a0583c9e36aef0f02435bd164fe172b1d3';

    // Create signed SC data for wert-widget
    // Please do this on backend
    const signedData = signSmartContractData(
      {
        address: userAddress, // user's address
        commodity: 'POL',
        commodity_amount: 3, // the crypto amount that should be sent to the contract method
        network: 'amoy',
        sc_address: '0xAAC496808A678B834073FB3435857FdcF0dc186F', // your SC address
        sc_input_data,
      },
      privateKey
    );

    const nftOptions: Options['extra'] = {
      item_info: {
        author: 'Wert',
        image_url: 'http://localhost:8765/sample_nft.png',
        name: 'Wert Sample NFT',
        seller: 'Wert',
        header: 'Wert Sample header',
      },
    };

    const otherWidgetOptions: Options = {
      partner_id: '01GCRJZ1P7GP32304PZCS6RSPD', // your partner id
      click_id: uuidv4(), // unique id of purchase in your system
      origin: 'https://sandbox.wert.io', // this option needed only in sandbox
      extra: nftOptions,
    };

    const wertWidget = new WertWidget({
      ...signedData,
      ...otherWidgetOptions,
    });

    wertWidget.open();
  })();
}
```

### Widget Initialization SmartContrac Example

```typescript
import WertWidget from '@wert-io/widget-initializer';
import type { Options } from '@wert-io/widget-initializer/types';
import { signSmartContractData } from '@wert-io/widget-sc-signer';
import { v4 as uuidv4 } from 'uuid';
import { Buffer } from 'buffer/';

window.Buffer = Buffer; // needed to use `signSmartContractData` in browser

/* We advise you not to use the private key on the frontend
   It is used here for example only
*/
const privateKey =
  '0x57466afb5491ee372b3b30d82ef7e7a0583c9e36aef0f02435bd164fe172b1d3';

const signedData = signSmartContractData(
  {
    address: '0x96D5990185022212d367A0e09263B12Dbb4EE06A',
    commodity: 'ETH',
    network: 'sepolia',
    commodity_amount: 0.005,
    sc_address: '0x56bB50a9912B04A5Ff260950F98A9dc023906f94',
    sc_input_data:
      '0x9dae76ea000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  },
  privateKey
);

const otherWidgetOptions: Options = {
  partner_id: '01GCRJZ1P7GP32304PZCS6RSPD',
  click_id: uuidv4(), // unique id of purchase in your system
  origin: 'https://sandbox.wert.io', // this option needed only for this example to work
  listeners: {
    loaded: () => console.log('loaded'),
  },
};

const wertWidget = new WertWidget({
  ...signedData,
  ...otherWidgetOptions,
});

wertWidget.open();
}
```

### TypeScript Definition Types

```typescript
// Main options types
export type Options = {
  partner_id: string;
  click_id?: string;
  origin?: string;
  lang?: string;
  address?: string;
  theme?: ThemeType;
  brand_color?: string;
  currency_amount?: number;
  country_of_residence?: string;
  state_of_residence?: string;
  date_of_birth?: string;
  full_name?: string;
  commodity?: string;
  commodity_amount?: number;
  network?: string;
  commodities?: string;
  currencies?: string;
  phone?: string;
  user_id?: string;
  email?: string;
  redirect_url?: string;
  support_url?: string;
  extra?: ExtraOptions;
  listeners?: EventListeners<WidgetEvents>;
  skip_init_navigation?: boolean;
  is_crypto_hidden?: boolean;
  session_id?: string;
  terms_on_payment?: boolean;
  payment_method?: string;
  payment_method_restriction?: boolean;
  display_currency?: string;
  hide_fee_breakdown?: boolean;
  is_auto_logout_on_order_complete_enabled?: boolean;
  is_session_expire_on_email_mismatch?: boolean;
} & CardBillingAddressOptions & SCOptions;

type CardBillingAddressOptions = {
  card_country_code?: string;
  card_city?: string;
  card_state_code?: string;
  card_post_code?: string;
  card_street?: string;
}
type SCOptions = {
  sc_address?: string;
  sc_input_data?: string;
  signature?: string;
};

export interface ExtraOptions {
  item_info?: ItemInfo;
  wallets?: Wallet[];
}

interface ItemInfo {
  author_image_url?: string;
  author?: string;
  image_url?: string;
  name?: string;
  category?: string;
  seller?: string;
  header?: string;
}
interface Wallet {
  name: string;
  network: string;
  address: string;
}

type ThemeType = 'dark' | undefined;

export type SetThemeParameters = {
  theme?: ThemeType;
  brand_color?: string;
};

// Event Types
export type EventTypes =
  | 'close'
  | 'error'
  | 'loaded'
  | 'payment-status'
  | 'position'
  | 'rate-update';

export type InternalEventTypes = '3ds-start' | '3ds-end';

interface WidgetEvent<EventType extends EventTypes | InternalEventTypes> {
  type: EventType;
}

type CloseEvent = WidgetEvent<"close">

type LoadedEvent = WidgetEvent<"loaded">

interface ErrorEvent extends WidgetEvent<"error"> {
  data: { name: string; message: string };
}

interface PaymentStatusEvent extends WidgetEvent<"payment-status"> {
  data: {
    status: string;
    payment_id: string;
    order_id: string;
    tx_id?: string;
    external_order_id: string;
  };
}

interface PositionEvent extends WidgetEvent<"position"> {
  data: { step: string };
}

interface RateUpdateEvent extends WidgetEvent<"rate-update"> {
  data: {
    ticker: string;
    fee_percent: string;
    currency_amount: string;
    fee_amount: string;
    commodity_amount: string;
    purchase_amount: string;
    miner_fee: string;
    currency_miner_fee: string;
  };
}

type Start3dsEvent = WidgetEvent<"3ds-start">
type End3dsEvent = WidgetEvent<"3ds-end">

export type WidgetEvents =
  | CloseEvent
  | ErrorEvent
  | LoadedEvent
  | PaymentStatusEvent
  | PositionEvent
  | RateUpdateEvent;

export type InternalWidgetEvents = Start3dsEvent | End3dsEvent;

type EventListeners<Events extends { type: string; data?: Record<string, unknown> }> = {
  [E in Events as E["type"]]?: E extends { data: Record<string, unknown> } ? (event: E["data"]) => any : () => any;
}
```
````

<br />