# Overview

KinoraSDK.

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FGewy2tQ5mmPl1hXSXoYr%2FDALL%C2%B7E%202023-10-24%2021.41.22%20-%20Hyperrealist%20photo%20at%2016%209%20of%20an%20expansive%20operations%20room%20where%20mid-century%20modernism%20meets%20retro%20Sovietwave%20designs.%20The%20architecture%20is%20dominated%20b.png?alt=media&amp;token=f7607be8-256d-46a5-982d-323c47d88fe9" alt=""><figcaption></figcaption></figure>

The **KinoraSDK** combines on-chain Livepeer videometric logging with decentralized social quests on Lens Protocol.&#x20;

Players engage with each Quest publication, accruing and claiming ERC20 and ERC721 token rewards as they elevate their videometric scores and fulfil designated milestones. &#x20;

Every interaction amplifies network engagement and individual progression, refining a player's on-chain reputation, aligning their content, feed, and media interests, and steering them towards heightened autonomy, in real-time.

The SDK delineates three principle segments: **Envoker, Dispatch** and the **Kinora Player Wrapper**. Each segment can be used in isolation for greater flexibility in programming Quest deployment and player interaction.

### [Envoker](/sdk-reference/envoker)

The Envoker class simplifies Quest setup.

It handles deployment and instantiation from the Kinora Contract Suite, publishing to Lens Protocol with integrated Kinora Open Actions and the configuration of milestones, escrowed rewards and token gated participation.

### [Dispatch](/sdk-reference/dispatch)

The Dispatch class orchestrates player engagements with Quests, encompassing the joining of quests pursuant to entry conditions, and the accomplishment of milestones to obtain rewards upon satisfying milestone completion requisites.

### [Kinora Player Wrapper](/sdk-reference/kinora-player-wrapper/kinora-player-wrapper)

The Kinora Player Wrapper, a React Function Component, extends the functionality of the Livepeer Player Component to facilitate optionally encrypted videometric logging for a connected Lens Profile.

It empowers personalized on-chain video feeds, links to quest milestone criteria, and grants meticulous control over style properties, callback properties, and instance methods.

These improvements enable deeper media integration, more efficient state management, customized visual layout options, and a direct interface with corresponding Lens Protocol data.


# Quickstart

The Quickest Route to Getting Started.

## Install the SDK

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

```
# Install via NPM
npm i kinora-sdk
```

{% endtab %}

{% tab title="yarn" %}

```
# Install via yarn
yarn add kinora-sdk
```

{% endtab %}
{% endtabs %}

### [Envoker](/sdk-reference/envoker)

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>import { Envoker } from "kinora-sdk";
</strong>
const apolloClient = new ApolloClient({
  link: new HttpLink({ uri: 'https://api.lens.xyz/graphql' }),
  headers: {
    "x-access-token": `Bearer ${authToken}`,
  },
  cache: new InMemoryCache(),
});

const lensProvider = new ethers.providers.JsonRpcProvider(
    "https://lensprovider.com",
    232,
  );

const newEnvoker = new Envoker({
    authedApolloClient: client,
    ipfsConfig: {
      uploadEndpoint: "https://api.pinata.cloud/pinning/pinJSONToIPFS",
      gateway: "https://gateway.pinata.cloud",
      headers: {
      Authorization: "Bearer YOUR_PINATA_JWT"
      }
    },
    envokerLensAddress: "0xlensprofileaddress",
    wallet: new ethers.Wallet(process.env.ENVOKER_PRIVATE_KEY, lensProvider)
});

const { postId, factoryId, questId, transactionHash, factoryQuestData } = await newEnvoker.instantiateNewQuest({
  factoryId: 0,
  questDetails: {
    title: "Chromadin Chronicle",
    description: "Engage in a Chromadin video binge session for Season 1 and Season 2 of The Dial Pirate Radio . Interactions, mirrors and comments on episodes accrue bonus points.",
    cover: "ipfs://QmQk9TqFivUqc6ktosoZVVih9o1uiY3r5Z7F3GCC1FpaJS", 
    },
  maxPlayerCount: 100, 
  milestones,
  joinQuestTokenGatedLogic: tokenGatedLogic,
});
</code></pre>

### [Dispatch](/sdk-reference/dispatch)

{% code overflow="wrap" fullWidth="true" %}

```typescript
import { Dispatch } from "kinora-sdk";

const apolloClient = new ApolloClient({
  link: new HttpLink({ uri: 'https://api.lens.xyz/graphql' }),
  headers: {
    "x-access-token": `Bearer ${authToken}`,
  },
  cache: new InMemoryCache(),
});

const lensProvider = new ethers.providers.JsonRpcProvider(
    "https://lensProvider.com",
    232,
  );

const newDispatch = new Dispatch({
    playerAuthedApolloClient: client
});

await newDispatch.playerJoinQuest(
    postId,
    new ethers.Wallet(process.env.PLAYER_PRIVATE_KEY, lensProvider));
```

{% endcode %}

### [Kinora Player Wrapper](/sdk-reference/kinora-player-wrapper/kinora-player-wrapper)

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>import { Player } from "@livepeer/react";
</strong>import dynamic from "next/dynamic";
import { useWalletClient } from 'wagmi';
import { KinoraProvider, KinoraPlayerWrapper } from "kinora-sdk";
import { apolloClient } from "../../lib/lens/client";
import { createReactClient, studioProvider, LivepeerConfig,} from "@livepeer/react";

const livepeerClient = createReactClient({
  provider: studioProvider({
    apiKey: process.env.LIVEPEER_STUDIO_KEY!,
  }),
});
 
function App() {
  return (
    &#x3C;LivepeerConfig client={livepeerClient}>
      &#x3C;KinoraProvider playerAuthedApolloClient={apolloClient}    ipfsConfig={{
    uploadEndpoint: "https://api.pinata.cloud/pinning/pinJSONToIPFS",
    gateway: "https://gateway.pinata.cloud",
    headers: { Authorization: "Bearer YOUR_JWT" }
  }}>
        &#x3C;Component {...pageProps} />
      &#x3C;/KinoraProvider>
    &#x3C;/LivepeerConfig>
  )
}


function Page() {

return (
  &#x3C;div id="parentId" className="w-20 h-20 flex">
    &#x3C;KinoraPlayerWrapper
      parentId={"parentId"}
      postId={postId}
      customControls={true}
      fillWidthHeight={true}
      >
      {(setMediaElement: (node: HTMLVideoElement) => void) => (
        &#x3C;Player
         mediaElementRef={setMediaElement}
         playbackId="f5eese9wwl88k4g8"
         objectFit="cover"
         />
       )}
    &#x3C;/KinoraPlayerWrapper>
   &#x3C;/div>
  );
}
</code></pre>


# Contracts

Deployed Contract Addresses.

The Kinora Contract Suite is deployed on `Lens Mainnet`. Depreciated versions are still accessable on `Polygon Mumbai` and `Mainnet`networks.

### **Lens Mainnet Addresses**

| Contract                                                       | Address                                                                                                                    |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| <mark style="color:blue;">Kinora Open Action</mark>            | [0x0Dc9Fb58651A51c3253350008cFF4D8878ca3761](https://explorer.lens.xyz/address/0x0Dc9Fb58651A51c3253350008cFF4D8878ca3761) |
| <mark style="color:blue;">Kinora Milestone Metric Logic</mark> | [0xA281d4031f5B86a4DdecDf057CD90110bBdecbbA](https://explorer.lens.xyz/address/0xA281d4031f5B86a4DdecDf057CD90110bBdecbbA) |

***

Depreciated Contracts Lens V2:

### **Polygon Mainnet Addresses**

<table data-full-width="true"><thead><tr><th width="265">Contract</th><th>Address</th></tr></thead><tbody><tr><td><mark style="color:blue;">Kinora Quest Data Instance</mark></td><td><a href="https://polygonscan.com/address/0xA9176a6F27d77668f388F2725193875984dA544F">0xA9176a6F27d77668f388F2725193875984dA544F</a></td></tr><tr><td><mark style="color:blue;">Kinora NFT Creator Instance</mark></td><td><a href="https://polygonscan.com/address/0xec5F2f43299932561aC4bAE25db7691EBC1efD83">0xec5F2f43299932561aC4bAE25db7691EBC1efD83</a></td></tr><tr><td><mark style="color:blue;">Kinora Access Control Instance</mark></td><td><a href="https://polygonscan.com/address/0xf31a86F0c8A12ee70518573b624B5214b967319e">0xf31a86F0c8A12ee70518573b624B5214b967319e</a></td></tr><tr><td><mark style="color:blue;">Kinora Metrics Instance</mark></td><td><a href="https://polygonscan.com/address/0xe28316Ad5770781537677bA537Ac8119707bE0D7">0xe28316Ad5770781537677bA537Ac8119707bE0D7</a></td></tr><tr><td><mark style="color:blue;">Kinora Escrow Instance</mark></td><td><a href="https://polygonscan.com/address/0xD3438CaB2f870a936E39bf1e40F1E84608517f1d">0xD3438CaB2f870a936E39bf1e40F1E84608517f1d</a></td></tr><tr><td><mark style="color:blue;">Kinora Open Action</mark></td><td><a href="https://polygonscan.com/address/0x196f267A4aCA1243CCCB85AD7098D1fDA1D683CD">0x196f267A4aCA1243CCCB85AD7098D1fDA1D683CD</a></td></tr><tr><td><mark style="color:blue;">Kinora Milestone Metric Logic</mark></td><td><a href="https://polygonscan.com/address/0xA257CAAa0aC1aEe2aEE7ED824De5d2052FD284dA">0xA257CAAa0aC1aEe2aEE7ED824De5d2052FD284dA</a></td></tr></tbody></table>

### **Polygon Mumbai Addresses**

<table data-full-width="true"><thead><tr><th width="265">Contract</th><th>Address</th></tr></thead><tbody><tr><td><mark style="color:blue;">Kinora Quest Data Instance</mark></td><td><a href="https://mumbai.polygonscan.com/address/0xA25858ACf9A17BD22052CBC5fF9023151c8d7D33">0xA25858ACf9A17BD22052CBC5fF9023151c8d7D33</a></td></tr><tr><td><mark style="color:blue;">Kinora NFT Creator Instance</mark></td><td><a href="https://mumbai.polygonscan.com/address/0x74Aaf7138C2fCFE225e2A343ACe67e3AaE2c5B1e">0x74Aaf7138C2fCFE225e2A343ACe67e3AaE2c5B1e</a></td></tr><tr><td><mark style="color:blue;">Kinora Access Control Instance</mark></td><td><a href="https://mumbai.polygonscan.com/address/0x20C0800fcBcdD09D2a84a3e8815Edf5E17B0e172">0x20C0800fcBcdD09D2a84a3e8815Edf5E17B0e172</a></td></tr><tr><td><mark style="color:blue;">Kinora Metrics Instance</mark></td><td><a href="https://mumbai.polygonscan.com/address/0x3B6616C525B475f42733f88C98d6A0BEDbCfe24E">0x3B6616C525B475f42733f88C98d6A0BEDbCfe24E</a></td></tr><tr><td><mark style="color:blue;">Kinora Escrow Instance</mark></td><td><a href="https://mumbai.polygonscan.com/address/0xe40f9BB1e9ad1D9Bd2aC841f167E47BABfD4C031">0xe40f9BB1e9ad1D9Bd2aC841f167E47BABfD4C031</a></td></tr><tr><td><mark style="color:blue;">Kinora Open Action</mark></td><td><a href="https://mumbai.polygonscan.com/address/0x43a18a3596d3C80467236b588976C9F11D166894">0x43a18a3596d3C80467236b588976C9F11D166894</a></td></tr><tr><td><mark style="color:blue;">Kinora Milestone Metric Logic</mark></td><td><a href="https://mumbai.polygonscan.com/address/0xf44B9EB115f5343f783aC4ffcF484CE570b4c1C1">0xf44B9EB115f5343f783aC4ffcF484CE570b4c1C1</a></td></tr></tbody></table>


# Subgraphs

Deployed Subgraphs.

{% hint style="warning" %}
With only 24 days notice, The Graph has stopped indexing Lens Chain 232 :disappointed\_relieved:. The schema persists for use in the SDK. And, new decentralised and open options are the works.
{% endhint %}

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FisNflSP3Myu7BSYXuWKq%2Fimage.png?alt=media&amp;token=86ddcec6-3b85-4ca8-8356-9f7ba611d5ff" alt=""><figcaption></figcaption></figure>

A public hosted subgraph for constructing queries and retrieving data from the Kinora Quest Data Contract is available for both Polygon Mainnet and Mumbai suites.

The SDK provides a series of [helper functions](/sdk-reference/helpers) for querying the data, however, for more complex and comprehensive queries, it's suggested to create your own from the available schema.

* [Kinora Lens Mainnet Subgraph](https://thegraph.com/explorer/subgraphs/Ajf3LcbRNx92R25fVFaUMVxQTUafksyQXLVdLXAoaYqD?view=Query\&chain=arbitrum-one)

Depreciated V2 Subgraphs.

* [Kinora Mumbai Subgraph](https://thegraph.com/hosted-service/subgraph/digitalax/kinora-mumbai)
* [Kinora Mainnet Subgraph](https://thegraph.com/hosted-service/subgraph/digitalax/kinora)


# Open Source Code

Live implementations.

Fork, use, copy, remix live implementations of the SDK in:

* [Kinora No Code](https://github.com/DIGITALAX/Kinora-App)
* [Cypher Search](https://github.com/DIGITALAX/Cypher-Search)
* [Chromadin](https://github.com/digitalax/chromadin)


# Envoker

Full Quest Set Up Steps.

{% content-ref url="/pages/MqcZHMJpxzBzgBGyI5Di" %}
[Instantiate Envoker](/sdk-reference/envoker/instantiate-envoker)
{% endcontent-ref %}

{% content-ref url="/pages/Dx0m97Zn9kl8uKAU7XVo" %}
[Instantiate New Quest](/sdk-reference/envoker/instantiate-new-quest)
{% endcontent-ref %}

{% content-ref url="/pages/Hp7XtAiG276KvZ1Wksdr" %}
[Terminate & Withdraw](/sdk-reference/envoker/terminate-and-withdraw)
{% endcontent-ref %}

{% content-ref url="/pages/YZenUYi3ExEzdjMwSeRY" %}
[Delete Quest](/sdk-reference/envoker/delete-quest)
{% endcontent-ref %}

{% content-ref url="/pages/WMVRTokwzVy2zmR3dGIm" %}
[Verify Player Milestone](/sdk-reference/envoker/verify-player-milestone)
{% endcontent-ref %}


# Instantiate Envoker

Create the Envoker Instance.

Initiate the Envoker instance to create a new Quest. These functions eliminate the need for manual encoding of the correct Quest data for the Kinora Open Action, although these operations can also be performed outside the SDK if it aligns better with your Lens App architecture.

{% code overflow="wrap" fullWidth="true" %}

```typescript
import { Envoker } from "kinora-sdk";

const apolloClient = new ApolloClient({
  link: new HttpLink({ uri: 'https://api.lens.xyz/graphql' }),
  headers: {
    "x-access-token": `Bearer ${authToken}`,
  },
  cache: new InMemoryCache(),
});

const lensProvider = new ethers.providers.JsonRpcProvider(
    "https://lensprovider.com",
    232,
  );

const newEnvoker = new Envoker({
    authedApolloClient: client,
    ipfsConfig: {
      uploadEndpoint: "https://api.pinata.cloud/pinning/pinJSONToIPFS",
      gateway: "https://gateway.pinata.cloud",
      headers: {
        Authorization: "Bearer YOUR_PINATA_JWT"
      }
    },
    envokerLensAddress: "0xlensprofileaddress",
    wallet: new ethers.Wallet(process.env.ENVOKER_PRIVATE_KEY, lensProvider)
});
```

{% endcode %}

**Envoker Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Lens authenticated Apollo Client. This can be the same client used for other Lens interactions within your App. It is used for publishing the Quest to your Lens feed through the SDK.*/
authedApolloClient: ApolloClient<NormalizedCacheObject>;

/* IPFS Config. The upload endpoint, gateway and required auth headers for your IPFS client i.e. Infura, Pinata etc.*/
ipfsConfig: {
  uploadEndpoint: string;
  gateway: string;
  headers?: Record<string, string>;
};

/* (Optional) Wallet instance for Polygon Network. You can also pass the wallet instance later when calling individual functions. */
wallet?: ethers.Wallet;

/* (Optional) Lens Profile Address of the envoker. You can also pass the profile address later when calling individual functions. */
envokerLensAddress: `0x${string}`;

/* (Optional) Existing Kinora Escrow Contract from a previously  Factory instantiation. */
kinoraEscrowContract?: `0x${string}`;

/* (Optional) Existing Kinora Metrics Contract from a previously  Factory instantiation. */
kinoraMetricsContract?: `0x${string}`;
```

{% endcode %}


# Instantiate New Quest

Configure a New Quest.

Instantiate a new Quest with your Quest details, specify the maximum number of players that can join a Quest and configure your [Milestone](#milestones) and [Token Gated Logic](#token-gated-logic).

Set the `factoryId` to 0 to create a new Kinora Factory instantiation. This will deploy your custom suite of the KinoraAccessControls, KinoraEscrow, KinoraMetrics, KinoraNFTCreator and KinoraQuestData contracts.

If you already have instantiated contracts from the factory suite, set your factoryId to create more quests within this suite. You can verify, add and remove additional wallet addresses as whitelisted envokers within your suite.

{% code overflow="wrap" fullWidth="true" %}

```typescript
const { postId, factoryId, questId, transactionHash, error, errorMessage, factoryAccessControls, factoryEscrow, factoryQuestData, factoryMetrics, factoryNFTCreator } = await newEnvoker.instantiateNewQuest({
  factoryId: 0,
  questDetails: {
    title: "Chromadin Chronicle",
    description:
      "Engage in a Chromadin video binge session for Season 1 and Season 2 of The Dial Pirate Radio . Interactions, mirrors and comments on episodes accrue bonus points.",
    cover: "ipfs://QmQk9TqFivUqc6ktosoZVVih9o1uiY3r5Z7F3GCC1FpaJS",
  },
  maxPlayerCount: 100,
  milestones, // see Milestones for struct configuration
  joinQuestTokenGatedLogic: tokenGatedLogic // see Token Gated Logic for struct configuration
});
```

{% endcode %}

## Instantiate Quest Props

{% code overflow="wrap" %}

```typescript
/* The Factory Id. Set to 0 for instantiating a new Kinora Factory instance, otherwise specify the Id that you are a verified envoker for.*/
factoryId: number;

/* The Quest details struct. The description and cover will be shown within the Lens Post across Lens interfaces. */
questDetails: { title: string; description: string; cover: `ipfs://${string}` };

/* The max number of unique players that can join the quest */
maxPlayerCount: number;

/* Factory Access Controls Contract. */
factoryAccessControls: `0x${string}`; 
 
/* Factory Escrow Contract. */
factoryEscrow: `0x${string}`; 

/* Factory Quest Data Contract. */
factoryQuestData: `0x${string}`; 

/* Factory Metrics Contract. */
factoryMetrics: `0x${string}`; 

/* Factory NFT Creator Contract. */
factoryNFTCreator: `0x${string}`;

/* All milestones within the Quest. See Milestone struct details for how to configure. */
milestones: Milestone[];

/* Token Gated logic for joining the Quest. See Token Gated Logic for struct configuration. */
joinQuestTokenGatedLogic: GatingLogic;

/* (Optional) Wallet instance for Polygon Network. If you have passed the wallet object to the constructor, it is not necessary here. */
wallet?: ethers.Wallet;

/* (Optional) Set to true to approve ERC20 reward tokens to be transfered to the KinoraEscrow contract through the SDK. If set to false, ensure to approve your token spend with the KinoraOpenAction Contract before calling instantiateNewQuest. */
approveRewardTokens?: ethers.Wallet;
```

{% endcode %}

## Response Object

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* If no error is thrown, returns the Post Id of the Lens Quest Publication envoked. */
postId?: string;

/* The factory Id of the Kinora Factory Instance used to create the new quest. Use this for instantiating new Quests within the same group of factory contracts. */
factoryId?: number;

/* Id of the quest instantiated within the Factory contracts. */
questId?: number;

/* If no error is thrown, returns the Transactions Hash of the Lens Quest Publication transaction. */
transactionHash?: `0x${string}`;

/* Boolean to indicate whether an error was encountered during instantiation. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
```

{% endcode %}

## Token Gated Logic

Every Quest and its subsequent Milestones may be configured with optional Token-Gated Logic.&#x20;

This logic verifies if a player possesses the specified configuration of ERC721 and/or ERC20 tokens on <mark style="background-color:blue;">Polygon Mainnet</mark> in their wallet, determining their eligibility to join a Quest and meet completion requirements for Milestones.

{% hint style="info" %}
Ensure that the length of your ERC20 Addresses array aligns with that of your ERC20 Thresholds array, with corresponding indices. Likewise, ensure alignment between your ERC721 Addresses and ERC721 Token IDs and/or ERC721 Token URIs, should IDs or URIs be specified. If no Token IDs or Token URIs are specified at a given index (i.e., an empty array is input), then all Token IDs for that contract will be accepted.
{% endhint %}

{% hint style="warning" %}
Threshold values for ERC20 tokens need to be indicated in wei. Ensure the correct wei value is provided for the specified ERC20 token.
{% endhint %}

{% code overflow="wrap" fullWidth="true" %}

```typescript
const tokenGatedLogic = {
  erc721TokenURIs: [[], [], ["ipfs://QmamNo25wv4ZQpmp14uXHxJnmp8GE6QNFA1fCug531TPoH", "ipfs://QmciaqC7cntDVvxxXHBYWWzBVRZQrGajHRZ3ydbVUcaUaH"]], 
  erc721TokenIds: [[], ["3", "13", "130"]], 
  erc721Addresses: ["0x453511e08F3AF28F0A47620bb5f32479F4E2e280", "0x1ACeCeDBC54d65D72338A2c0b55b479aF5B45870", "0x0147435c505390Bb1E657c8EBc373DcEdfDe0F08"],
  erc20Addresses: ["0x6968105460f67c3bf751be7c15f92f5286fd0ce5"],
  erc20Thresholds: ["10000000000000000000"],
  oneOf: true,
}
```

{% endcode %}

#### Gated Logic Props

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript">/* The specific token URIs within a ERC721 contract that must be held. Leave an empty array if all token URIs within a contract are valid. This form is only valid for NFT Contracts that inherent IERC721Enumerable interface. It exists to support NFT collection structures that mint from the same NFT contract. */
erc721TokenURIs: string[][];

/* The specific token Ids within a ERC721 contract that must be held. Leave an empty array if all token Ids within a contract are valid.*/
erc721TokenIds: number[][];
<strong>
</strong><strong>/* The Polygon Mainnet contract addresses of the ERC721 compatible contract addresses. */
</strong><strong>erc721Addresses: `0x${string}`[];
</strong>
/* The Polygon Mainnet contract addresses of the ERC20 compatible contract addresses. */
erc20Addresses: `0x${string}`[];

/* The specific threshold values, in wei, that must be held of the ERC20 tokens. */
erc20Thresholds: string[];

/* Set oneOf to true if a player only needs to hold one of the specified ERC721 or ERC20 tokens. Set oneOf to false if they must hold all specified tokens. */
oneOf: boolean;
</code></pre>

## Milestones

Configure the reward and eligibility requirements for each Milestone within your Quest. You can specify as many Milestones as you like per Quest.

For a player to be eligible to complete a Milestone, you can specify both Token Gated Logic for the Milestone as well as video metric thresholds.

{% code overflow="wrap" fullWidth="true" %}

```typescript
const milestones = [{
  gated: tokenGatedLogic,
  reward: milestoneOneReward,
  milestone: 1,
  details: milestoneOneDetailsObject,
  eligibility: milestoneOneEligibilityObject
}, 
{
  gated: tokenGatedLogic,
  reward: milestoneTwoReward,
  milestone: 2,
  details: milestoneTwoDetailsObject,
  eligibility: milestoneTwoEligibilityObject
}]
```

{% endcode %}

#### **Milestone Props**&#x20;

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>/* The token gated logic that a player must meet to be eligible to complete a milestone. It follows the same struct set up as for above. */
</strong><strong>gated: GatingLogic; 
</strong><strong>
</strong><strong>/* The Reward struct, specifying the ERC20 or ERC721 rewards that a player receives upon milestone completion. See below for more details on how to configure Rewards. */
</strong>reward: Reward[]; 

/* The milestone order. Milestones are completed in ascending order i.e. Milestone 1 must be completed by a player before moving to Milestone 2. */
milestone: number;

/* The Milestone details struct and video post, title, description.*/
details: { title: string; 
           description: string; 
           cover: `ipfs://${string}`, 
           videoCovers: {
               cover: `ipfs://${string}` | undefined;
               title: string;
               description: string;
           }[]; 
};

/* The eligibility video metrics for each Livepeer video. See below for more details on how to configure Milestone Eligibility. */
eligibility: MilestoneEligibility;
</code></pre>

### **Milestone Eligibility**&#x20;

Milestone eligibility specifies the video metric criteria for each video within a milestone.

**Milestone Eligibility Props**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* An array detailing the video metric criteria a player must satisfy for a specific Livepeer video playback ID and the Lens Post Id associated with the video. 

Specify other factoryIds from other contract suites if you wish to include player videometrics logged on-chain from within these suites in the milestone eligibility calculation. If not, pass an empty array.

*/
internalCriteria?: {
    factoryIds: number[];
    playbackId: string;
    postId: string;
    playbackCriteria: MilestoneEligibilityCriteria;
}[];
```

{% endcode %}

**PlaybackCriteria Props**

All numerical playback props signify threshold minimums that a player must achieve for the respective video, if nothing is set the threshold defaults to 0. For boolean values, if it is set as false or not inputted, it is ignored as criteria.&#x20;

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Min. Average View Duration (AVD):
   The minimum mean time duration a player spends viewing a video (in seconds) across multiple viewings.
   It's calculated by summing the total view time and dividing by the total number of views. */
minAVD?: number;

/* Min. Video Play Count:
   The minimum number of times a video must be played from start to end without seeking. */
minPlayCount?: number;

/* Total Duration (Specify in Seconds)
   The minimum cumulative time duration of the video being played.
   It's calculated by summing the duration of each viewing session. */
minDuration?: number;

/* Quote the Publication:
   Boolean value indicating whether a player must quote the video publication. */
quote?: boolean;

/* Mirror the Publication:
   Boolean value indicating whether a player must mirror the video publication. */
mirror?: boolean'

/* Like the Publication:
   Boolean value indicating whether a player must like the video publication. */
react?: boolean;

/* Bookmark the Publication:
   Boolean value indicating whether a player must bookmark the video publication. */
bookmark?: boolean;

/* Comment the Publication:
   Boolean value indicating whether a player must comment on the video publication. */
comment?: boolean;

/* Min. Secondary Quote On Quote:
   The minimum number of quotes on the player's quote publication of the video. */
minSecondaryQuoteOnQuote?: number;

/* Min. Secondary Mirror On Quote:
   The minimum number of mirror on the player's quote publication of the video. */
minSecondaryMirrorOnQuote?: number;

/* Min. Secondary Comment On Quote:
   The minimum number of comment on the player's quote publication of the video. */
minSecondaryCommentOnQuote?: number;

/* Min. Secondary React On Quote:
   The minimum number of reacts on the player's quote publication of the video. */
minSecondaryReactOnQuote?: number;

/* Min. Secondary Collect On Quote:
   The minimum number of collects on the player's quote publication of the video. */
minSecondaryCollectOnQuote?: number;

/* Min. Secondary Quote On Comment:
   The minimum number of quotes on the player's comment publication of the video. */
minSecondaryQuoteOnComment?: number;

/* Min. Secondary Mirror On Comment:
   The minimum number of mirror on the player's comment publication of the video. */
minSecondaryMirrorOnComment?: number;

/* Min. Secondary Comment On Comment:
   The minimum number of comment on the player's comment publication of the video. */
minSecondaryCommentOnComment?: number;

/* Min. Secondary React On Comment:
   The minimum number of reacts on the player's comment publication of the video. */
minSecondaryReactOnComment?: number;

/* Min. Secondary Collect On Comment:
   The minimum number of collects on the player's comment publication of the video. */
minSecondaryCollectOnComment?: number;
```

{% endcode %}

### **Milestone Reward**

To encourage players towards Milestone completion, rewards are allocated in the form of either ERC20 or ERC721 tokens, which players can claim upon completion. Multiple rewards can be set per Milestone.&#x20;

The specified ERC20 tokens and their amounts must be contained in your wallet, as they are sent to the Kinora Escrow contract at the point of Quest instantiation, ensuring that players will receive their rewards.&#x20;

The amount sent to the Escrow contract is the amount specified for each Milestone multiplied by the maximum player count. For example if the Quest specifies a Maximum of 20 players and a Milestone ERC20 reward of 10 wei, then 20 x 10 wei will be sent to the Escrow contract to cover all Players.

Players can only withdraw and claim these tokens upon meeting the established milestone eligibility and being verified by the envoker through the `setPlayerEligibleToClaimMilestone` function. An emergency admin withdraw function is incorporated, which, when triggered, terminates the Quest and precludes players from continuing, as their rewards will no longer reside within the Escrow contract.

In the case of ERC721 rewards, you need to specify the token URI intended for the Milestone reward NFT. Upon valid achievement of a milestone, players will be eligible to mint the NFT from the Kinora NFT Creator contract.

{% hint style="warning" %}
You must approve the KinoraOpenAction Contract to transfer ERC20 rewards from your wallet to the KinoraEscrow Contract. You can do this manually, or set `approveRewardTokens` to **true** when calling `instantiateNewQuest` to approve directly through the SDK.
{% endhint %}

{% hint style="warning" %}
Remember that ERC20 token reward amounts must be specified in their correct wei amount.
{% endhint %}

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>import { RewardType } from "kinora-sdk"
</strong><strong>
</strong><strong>const milestoneOneReward = {
</strong><strong>  type: RewardType.ERC721,
</strong><strong>  erc721URI: "ipfs://QmRPLqEtcgyiuJZ6kvg6qTrrk7ki7s8arkpHBJGFYNwgeD",
</strong><strong>}
</strong><strong>
</strong><strong>const milestoneTwoReward = {
</strong><strong>  type: RewardType.ERC20,
</strong><strong>  erc20tokenAddress: "0x6968105460f67c3bf751be7c15f92f5286fd0ce5",
</strong>  erc20TokenAmount: "100000"
<strong>}
</strong></code></pre>

**Reward Props**

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>/* The Reward Type. Either ERC721 or ERC20. */
</strong><strong>type: RewardType; 
</strong><strong>
</strong><strong>/* The token URI ipfs hash of the NFT. Leave empty if type is ERC20. */
</strong>erc721URI?: `ipfs://${string}`;
<strong>
</strong><strong>/* The Polygon Mainnet contract address of the ERC20 token. Leave empty if type is ERC721. */
</strong>erc20tokenAddress?: `0x${string}`; 

/* Must be specified in wei. Amount of the ERC20 token a player is rewarded upon completion. Leave empty if type is ERC721. */
erc20TokenAmount?: string; 
</code></pre>


# Terminate & Withdraw

Emergency Terminate Quest & Withdraw Escrowed Rewards.

This function serves as an emergency measure to retrieve deposited ERC20 rewards from your Kinora Escrow Instance Contract.&#x20;

Upon activation of this function, your Quest will transition to a Closed status. This cessation prohibits new player entries and halts ongoing milestone completions for existing participants.&#x20;

{% code overflow="wrap" fullWidth="true" %}

```typescript
const {txHash, error, errorMessage } = await newEnvoker.terminateQuestAndWithdraw(
    1,
);
```

{% endcode %}

### Terminate & Withdraw Props

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The Quest Id to close. If you need to find the Quest Id from the Lens Publication Id, you can call getQuestIdFromPublication from the Helpers functions or call getQuestIdFromLensData directly to the KinoraQuestData contract. */
questId: number;

/* (Optional) Existing Kinora Escrow Contract from a previously  Factory instantiation. */
kinoraEscrowContract?: `0x${string}`;

/* (Optional) Wallet instance for Polygon Network. If you have passed the wallet object to the constructor, it is not necessary here. */
wallet?: ethers.Wallet;
```

{% endcode %}

### Response Object

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Transaction hash for Quest termination. */
txHash?: string;

/* Boolean to indicate whether an error was encountered during termination. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
```

{% endcode %}


# Delete Quest

Delete Quest & withdraw remaining rewards.

This function Deletes a given quest and withdraws any remainder ERC20 token rewards left in your Escrow contract that have not yet been claimed by a Player upon milestone completion.

Upon activation of this function, your Quest will be deleted from both your Quest Data contract and the Lens protocol feed.

{% code overflow="wrap" fullWidth="true" %}

```typescript
const {txHash, error, errorMessage } = await newEnvoker.deleteQuest(
    1,
);
```

{% endcode %}

### Terminate & Withdraw Props

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The Quest Id to close. If you need to find the Quest Id from the Lens Publication Id, you can call getQuestIdFromPublication from the Helpers functions or call getQuestIdFromLensData directly to the KinoraQuestData contract. */
questId: number;

/* (Optional) Existing Kinora Escrow Contract from a previously  Factory instantiation. */
kinoraEscrowContract?: `0x${string}`;

/* (Optional) Wallet instance for Polygon Network. If you have passed the wallet object to the constructor, it is not necessary here. */
wallet?: ethers.Wallet;
```

{% endcode %}

### Response Object

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Transaction hash for Quest deletion. */
txHash?: string;

/* Boolean to indicate whether an error was encountered during termination. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
```

{% endcode %}


# Verify Player Milestone

Player Milestone Completion Validation.

Before a player can claim rewards for milestone completion, the Quest Envoker must call `setPlayerEligibleToClaimMilestone` .&#x20;

Although the Player's video metric and token eligibility is verified within the KinoraOpenAction Contract, there is still potential for a player to send fabricated off-chain video metric data to the KinoraMetrics contract, and thus, an additional check aids in reducing this.

See [here](/architecture/data-corruption-mitigation) for more on how the SDK manages the risk of data corruption during the off-chain logging and transmission of metrics.

You can verify a player to claim a milestone before they have reached their metric targets, however the player will still not be able to claim their reward and mark the milestone as completed until they've reached the metric targets. To check a player's milestone eligibility against their logged video metrics and the milestone's video thresholds, see [`playerMilestoneEligibilityCheck`](/sdk-reference/dispatch/milestone-eligibility-check).

{% code overflow="wrap" fullWidth="true" %}

```typescript
await newEnvoker.setPlayerEligibleToClaimMilestone({
    1,
    1,
    "0x0106",
    true,
  });
```

{% endcode %}

**Verify Milestone Props**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The Quest Id to close. If you need to find the Quest Id from the Lens Publication Id, you can call getQuestIdFromPublication from the Helpers functions or call getQuestIdFromLensData directly to the KinoraQuestData contract. */
questId: string;

/* The milestone number to verify the player for. */
milestone: number;

/* The Lens profile address of the player being verified. */
playerProfile: `0x${string}`;

/* Set to true to verify the player to complete the milestone and claim the milestone rewards. */
eligible: boolean;

/* (Optional) Existing Kinora Metrics Contract from a previously  Factory instantiation. */
kinoraMetricsContract?: `0x${string}`;

/* (Optional) Wallet instance for Polygon Network. If you have passed the wallet object to the constructor, it is not necessary here. */
wallet?: ethers.Wallet;
```

{% endcode %}

### Response Object

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Transaction hash for Player verification. */
txHash?: string;

/* Boolean to indicate whether an error was encountered during verification. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
```

{% endcode %}


# Dispatch

Player Quest Interactions Set Up.

{% content-ref url="/pages/I2vBPBFJuYogmgQGWHVH" %}
[Instantiate Dispatch](/sdk-reference/dispatch/instantiate-dispatch)
{% endcontent-ref %}

{% content-ref url="/pages/G0g5hUYCBtryu7MuP1La" %}
[Join Quest](/sdk-reference/dispatch/join-quest)
{% endcontent-ref %}

{% content-ref url="/pages/osXxKIPKZLIbL0boN88v" %}
[Milestone Eligibility Check](/sdk-reference/dispatch/milestone-eligibility-check)
{% endcontent-ref %}

{% content-ref url="/pages/exNJcoCmc86NBp4tOV0p" %}
[Complete Milestone](/sdk-reference/dispatch/complete-milestone)
{% endcontent-ref %}


# Instantiate Dispatch

Create the Dispatch Instance.

Initiate the Dispatch instance to invoke the Player Join Quest and Complete Milestone functions. These functions eliminate the need for manual encoding of the correct Quest data for the KinoraOpenAction, and for accurately acting on the Lens publication, although these operations can also be performed outside the SDK if it aligns better with your Lens App architecture.

{% code overflow="wrap" fullWidth="true" %}

```typescript
import { Dispatch } from "kinora-sdk";

const apolloClient = new ApolloClient({
  link: new HttpLink({ uri: 'https://api.lens.xyz/graphql' }),
  headers: {
    "x-access-token": `Bearer ${authToken}`,
  },
  cache: new InMemoryCache(),
});


const newDispatch = new Dispatch({
    playerAuthedApolloClient: client
});
```

{% endcode %}

**Dispatch Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Lens authenticated Apollo Client. This can be the same client used for other Lens interactions within your App that the player is Authed for. It is used for enabling the Player to interact with the Quest publication Open Action.*/
playerAuthedApolloClient: ApolloClient<NormalizedCacheObject>;
```

{% endcode %}


# Join Quest

Enroll Player in Quest.

Players join a Quest by "Acting On" your Quest Publication on Lens protocol through the Kinora Open Action Contract. This action ensures their interaction is recorded both within the Kinora Contract Suite, where each player maintains a unique profile linked to their Lens profile ID, and additionally within Lens networks and communities.

{% code overflow="wrap" fullWidth="true" %}

```typescript
const lensprovider = new ethers.providers.JsonRpcProvider(
    "https://lensprovider.com",
    232,
  );

const {txHash, error, errorMessage } = await newDispatch.playerJoinQuest(
    "8776305547672229303710652256128036480640562614756544519403188376059",
    new ethers.Wallet(process.env.PLAYER_PRIVATE_KEY, lensprovider));
```

{% endcode %}

**Player Join Quest Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Enter the Lens postId of the invoked Quest. */
postId: string;

/* Wallet instance for the Player on Polygon Network. */
wallet: ethers.Wallet;
```

{% endcode %}

**Result Object:**

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>/* The txHash for acting on the Publication and joining the Quest. */
</strong>txHash?: `0x${string}`;

/* Boolean to indicate whether an error was encountered during joining. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
</code></pre>


# Milestone Eligibility Check

Check Player Milestone Metric Eligibility.

A helper function to verify the Player's on-chain video metrics against a Quest's milestone video metric thresholds.&#x20;

{% code overflow="wrap" fullWidth="true" %}

```typescript
const {eligible, completed, toComplete, error, errorMessage } = await newDispatch.playerMilestoneEligibilityCheck(
    "0x0106",
    1,
    1,
    factoryQuestData);
```

{% endcode %}

**Player Milestone Eligibility Check Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Player's profile address. */
playerProfile: `0x${string}`;

/* Quest Id. */
questId: number;

/* Milestone Id. */
milestone: number;

/* Kinora Quest Data Address. */
kinoraQuestDataAddress: `0x${string}`;
```

{% endcode %}

**Result Object:**

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>/* A boolean for if the player is eligible or not. */
</strong>eligible?: boolean;

/* An array including all completed activity for each video. */
completed?: PlayerVideoActivity[];

/* An array including all to be completed activity for each video. */
toComplete?: PlayerVideoActivity[];

/* Boolean to indicate whether an error was encountered during joining. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
</code></pre>

**PlayerVideoActivity Object:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Player metrics for the included milestone video. */
secondaryCollectOnComment: number;
secondaryCollectOnQuote: number;
secondaryCommentOnQuote: number;
secondaryCommentOnComment: number;
secondaryMirrorOnComment: number;
secondaryMirrorOnQuote: number;
secondaryQuoteOnComment: number;
secondaryQuoteOnQuote: number;
secondaryReactOnQuote: number;
secondaryReactOnComment: number;
hasReacted: boolean;
hasQuoted: boolean;
hasMirrored: boolean;
hasCommented: boolean;
hasBookmarked: boolean;
duration: number;
avd: number;
playCount: number;
```

{% endcode %}


# Complete Milestone

Player Milestone Completion and Reward Claim.

To complete a Milestone, players "Act On" the same Lens publication, where the KinoraOpenAction Contract assesses their eligibility to complete and claim the next Milestone on the Quest.&#x20;

If eligible, it automatically withdraws their ERC20 rewards from your Kinora Escrow Contract instance and/or mints to their wallet any reward ERC721 NFTs, marks the Milestone as complete, and, if there are remaining Milestones in the Quest, allows them to continue with their progress.

Players can only claim their rewards and complete a milestone if they have met all token gated and video metric conditions. See more on how eligibility is verified [here](/sdk-reference/envoker/verify-player-milestone).

If the Player is completing the final Milestone in a Quest, the contract will also verify the Player as having finished the Quest.

{% code overflow="wrap" fullWidth="true" %}

```typescript
const { txHash, error, errorMessage } = await newDispatch.playerCompleteQuestMilestone("8776305547672229303710652256128036480640562614756544519403188376059", new ethers.Wallet(process.env.PLAYER_PRIVATE_KEY, polygonProvider));
```

{% endcode %}

**Player Join Quest Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The postId of the invoked Quest. */
postId: string;

/* Wallet instance for the Player on Polygon Network. */
wallet: ethers.Wallet;
```

{% endcode %}

**Result Object:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The txHash for acting on the Publication and completing the milestone and/or Quest. */
txHash?: `0x${string}`;

/* Boolean to indicate whether an error was encountered during completion and rewards claim. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
```

{% endcode %}


# Kinora Player Wrapper

Record Video Metric Set Up.

{% content-ref url="/pages/9kJnb3Sz4r0HwWKENuxN" %}
[Kinora Provider](/sdk-reference/kinora-player-wrapper/kinora-provider)
{% endcontent-ref %}

{% content-ref url="/pages/rQGHX3Yr21c6T0hT45JZ" %}
[Kinora Player Wrapper](/sdk-reference/kinora-player-wrapper/kinora-player-wrapper)
{% endcontent-ref %}

{% content-ref url="/pages/8DbE2OFuGocSw7J5pnf1" %}
[Send Metrics On-Chain](/sdk-reference/kinora-player-wrapper/send-metrics-on-chain)
{% endcontent-ref %}


# Kinora Provider

Wrap your App.

Wrap your app with the Kinora Provider Component to correctly initialize the Kinora instance, passing in the player's authenticated Lens Apollo Client. In the case of utilizing NextJS, this component should be placed in the `App.tsx` pages folder of your application.&#x20;

Make sure to also set and configure the LivePeer Config.&#x20;

{% code overflow="wrap" fullWidth="true" %}

```typescript
import { KinoraProvider } from "kinora-sdk";
import { apolloClient } from "../../lib/lens/client";
import { createReactClient, studioProvider, LivepeerConfig,} from "@livepeer/react";

const livepeerClient = createReactClient({
  provider: studioProvider({
    apiKey: process.env.LIVEPEER_STUDIO_KEY!,
  }),
});
 
function App() {
  return (
    <LivepeerConfig client={livepeerClient}>
      <KinoraProvider playerAuthedApolloClient={apolloClient}  ipfsConfig={{
    uploadEndpoint: "https://api.pinata.cloud/pinning/pinJSONToIPFS",
    gateway: "https://gateway.pinata.cloud",
    headers: { Authorization: "Bearer YOUR_JWT" }
  }}>
        <Component {...pageProps} />
      </KinoraProvider>
    </LivepeerConfig>
  )
}
```

{% endcode %}

### Kinora Provider Props

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Lens authenticated Apollo Client. This can be the same client used for other Lens interactions within your App that the player is Authed for. It is used for enabling the Player to interact with the Quest publication Open Action.*/
playerAuthedApolloClient: ApolloClient<NormalizedCacheObject>;

/* IPFS Config. The upload endpoint, gateway and required auth headers for your IPFS client i.e. Infura, Pinata etc.*/
ipfsConfig: {
  uploadEndpoint: string;
  gateway: string;
  headers?: Record<string, string>;
};
```

{% endcode %}


# Kinora Player Wrapper

Wrap your Livepeer Players.

Wrap each Livepeer Player Component in your App with the Kinora Player Wrapper for seamless videometric logging.&#x20;

**The Kinora Player Wrapper performs two core functions:**&#x20;

1. [Metric Logging](#metric-logging): For crafting personalized video feeds and documenting Player Quest Milestone progression.&#x20;
2. [Refined Control](#refined-control): For Livepeer Player style customization and direct video element interaction via callback props and instance methods.

{% hint style="warning" %}
Some NextJS App Architectures may need to import the `KinoraPlayerWrapper` dynamically with **ssr: false** to ensure the wrapper is only rendered on the client side.
{% endhint %}

{% code overflow="wrap" fullWidth="true" %}

```typescript
import { Player } from "@livepeer/react";
import { KinoraPlayerWrapper } from "kinora-sdk";

function Page() {

return (
  <div id="uniqueParentId" className="w-20 h-20 flex">
    <KinoraPlayerWrapper
      parentId={"uniqueParentId"}
      postId={postId}
      customControls={true}
      fillWidthHeight={true}
      >
      {(setMediaElement: (node: HTMLVideoElement) => void) => (
        <Player
         mediaElementRef={setMediaElement}
         playbackId="f5eese9wwl88k4g8"
         objectFit="cover"
         />
       )}
    </KinoraPlayerWrapper>
   </div>
  );
}
```

{% endcode %}

### Metric Logging

To initiate the logging of player interactions for each unique playback Id played by your Livepeer Player Components, ensure to pass in the **`postId`** that the video is linked to (i.e. the post Id of the video on Lens). Metrics will not be logged if this prop is not provided.

Refer to the [<mark style="background-color:purple;">**Send Metrics On-Chain**</mark>](/sdk-reference/kinora-player-wrapper/send-metrics-on-chain) section for comprehensive instructions on accurately receiving and sending the transmission of logged player metrics on-chain.

### Refined Control

All generic Video HTML Element callback props and instance methods are readily configurable and accessible via the wrapper, enabling comprehensive style customization for controls, aspect ratio and general CSS styling of the Livepeer Player component.

If you possess the publication Id of a Lens Post associated with the playback Id, this prop can also be passed in along with the user's profile Id to simultaneously retrieve and display publication data related to the video.

### Kinora Player Wrapper Props

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* The Livepeer Player Component passed as an argument to the setMediaElement function, which is used to set a reference to the HTMLVideoElement. */
children: (
    setMediaElement: (node: HTMLVideoElement) => void,
  ) => React.ReactNode;
 
/* The Id assigned to the parent div surrounding the Kinora Player Wrapper. For each new Kinora Player Wrapper instantiated around a unique Livepeer Player Component provide a unique parent Id to ensure control is correctly assigned. */
parentId: string

/* Lens post Id associated with the video. */
postId?: string;

/** Triggered when the video starts to play */
onPlay?: (event: Event) => void;

/** Triggered when the video is paused */
onPause?: (event: Event) => void;

/** Triggered when the video is aborted either by the user or programmatically */
onAbort?: (event: Event) => void;

/** Triggered when the video is ready to start playing */
onCanPlay?: (event: Event) => void;

/** Triggered when the video can be played all the way through without stopping */
onCanPlayThrough?: (event: Event) => void;

/** Triggered when the duration of the video changes */
onDurationChange?: (event: Event) => void;

/** Triggered when the media element is reset and becomes empty */
onEmptied?: (event: Event) => void;

/** Triggered when the video has ended */
onEnded?: (event: Event) => void;

/** Triggered when an error occurs during the loading of a video */
onError?: (event: Event) => void;

/** Triggered when the browser has loaded the current frame of the video */
onLoadedData?: (event: Event) => void;

/** Triggered when the browser has loaded meta data for the video */
onLoadedMetadata?: (event: Event) => void;

/** Triggered when the browser starts looking for the video */
onLoadStart?: (event: Event) => void;

/** Triggered when the video is playing */
onPlaying?: (event: Event) => void;

/** Triggered when the browser is downloading the video */
onProgress?: (event: Event) => void;

/** Triggered when the playing speed of the video changes */
onRateChange?: (event: Event) => void;

/** Triggered when the user is finished moving/skipping to a new position in the video */
onSeeked?: (event: Event) => void;

/** Triggered when the user begins moving/skipping to a new position in the video */
onSeeking?: (event: Event) => void;

/** Triggered when the browser is trying to get media data, but data is not available */
onStalled?: (event: Event) => void;

/** Triggered when the browser is intentionally not getting media data */
onSuspend?: (event: Event) => void;

/** Triggered when the current playback position has changed */
onTimeUpdate?: (event: Event) => void;

/** Triggered when the volume changes */
onVolumeChange?: (event: Event) => void;

/** Triggered when the video is waiting for more data to continue playing */
onWaiting?: (event: Event) => void;

/** Triggered when the video enters or exits full screen mode */
onFullScreenChange?: (event: Event) => void;

/** Sets the volume level; 'level' is the volume level, 'id' is an identifier for the volume change request to avoid caching, you can use uuid() to generate a random value. The default value is 0.5.*/
volume?: { level: number; id: number };

/** Seeks to a specified time in the video; 'time' is the time in seconds, 'id' is an identifier for the seek request to avoid caching, you can use uuid() to generate a random value. */
seekTo?: { time: number; id: number };

/** Initiates video playback when set to true */
play?: boolean;

/** Pauses video playback when set to true */
pause?: boolean;

/** Displays the video to fullscreen when set to true */
fullscreen?: boolean;

/* Set true to overide the default Livepeer Player aspect ratio sizing and have the Video fill up the custom width and height styled in the parent div. */
fillWidthHeight?: boolean;

/* Set true to completely hide the Livepeer Player controls. */
customControls?: boolean;

/* A call back function to receive Lens publication data associated with the video data. */
onLensVideoData?: (
    data: Post | Mirror | Comment | Quote,
    error: ApolloError | undefined,
  ) => void;

/* Lens profile address of the player signed in on the App. Only required in onLensVideoData function is to be used. */
playerProfile?: string;

/* Custom CSS styles class to override default LivePeer Player styling. */
styles?: React.CSSProperties;
```

{% endcode %}


# Send Metrics On-Chain

Log Player Metrics & Automate On-Chain Signing.

Everytime a Player interacts with a configure video in the application, wrapped with the KinoraPlayerWrapper and utilizing the Livepeer React Player, their live metrics are stored and logged, and can be retrieved and displayed on your interface.

Each time a player engages with a configured video within your application, their interactions are captured in real-time. This is made possible through the KinoraPlayerWrapper, and Livepeer React Player. These live metrics can be called in real time and displayed on your interface using the `getLiveVideoMetrics` helper function specified in [Helpers](/sdk-reference/helpers).&#x20;

Players have the discretion to upload these engagement metrics onto the blockchain for any specific video post. When sent on-chain,  their interactions contribute meaningfully towards any ongoing quests. These metrics are also cumulative, where when a player interacts with a video that's linked to a Lens post and is relevant to multiple quests, their activity is concurrently logged across all quests associated with that video.&#x20;

In scenarios where identical videos are featured in multiple areas of your application, ensure that the correct postId is consistently assigned to the wrapper for each video. This allows for amassing video metrics on an application-wide level.

{% hint style="warning" %}
Note that only players who have previously joined a Quest with Kinora are verified for metric logging, as they would have undergone verification to create their Player profile with the contract. You can query the `KinoraQuestData` Contract to verify whether a Player has a valid profile or not with Kinora.
{% endhint %}

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript">import { Kinora } from "kinora-sdk";

<strong>const kinora = Kinora.getInstance(apolloClient, {
</strong>    uploadEndpoint: "https://api.pinata.cloud/pinning/pinJSONToIPFS",
    gateway: "https://gateway.pinata.cloud",
    headers: {
      Authorization: "Bearer YOUR_PINATA_JWT"
    }
  });

const collectAndSendMetrics = async () => {
      
      // signer object of the Player
      await (window as any).ethereum.request({ method: "eth_requestAccounts" });
      const provider = new ethers.providers.Web3Provider(
        (window as any).ethereum,
        232
      );
      const signer = provider.getSigner();

      // send metrics on-chain
      const { txHash, error, errorMessage } = await kinora.sendPlayerMetricsOnChain(
        "0x79-0x06",
        "0x31",
        signer as ethers.Wallet,
        kinoraMetricsContractAddress,
        kinoraQuestDataContractAddress,
      );
}
</code></pre>

**Send Metrics On-Chain Props:**

{% code overflow="wrap" fullWidth="true" %}

```typescript
/* Enter the Video postId related to the metrics to be sent on-chain. */
postId: string;

/* Enter the Player's Lens Profile address. */
playerProfile: `0x${string}`;

/* Wallet instance for the Player on Lens Network. */
wallet: ethers.Wallet;

/* Instantiated Kinora Metrics Contract. */
kinoraMetricsContractAddress: `0x${string}`;

/* Instantiated Kinora Quest Data Contract. */
kinoraQuestDataContractAddress: `0x${string}`;
```

{% endcode %}

**Result Object:**

<pre class="language-typescript" data-overflow="wrap" data-full-width="true"><code class="lang-typescript"><strong>/* The txHash for sending metrics on-chain. */
</strong>txHash?: `0x${string}`;

/* Boolean to indicate whether an error was encountered during sending the metrics. */
error: boolean;

/* Message of the encountered error. */
errorMessage?: string;
</code></pre>


# Helpers

Helper Functions for Quest Data Query.

The SDK provides a series of helper functions for querying Quest data, however, for more complex and comprehensive queries, it's suggested to create your own from the available [Subgraph schema](/subgraphs).

{% code overflow="wrap" %}

```typescript
/* Call all helper functions through the Kinora Instance and have the Kinora Provider set in the router of your app.*/
import { Kinora } from "kinora-sdk";

const kinora = Kinora.getInstance(apolloClient);

/* Get the Live Video Metrics for each video. Pass the same post Id passed to the KinoraPlayerWrapper of the video metrics you'd like to retrieve. It returns the current live session playcount, avd, total duration, total interactions and the most replayed areas as a list of 1 second string segments (in order of most replayed). */
await kinora.getLiveVideoMetrics(postId);

/* Used to retrieve the secondary data interactions of the player with a video post to check if they've met secondary comment / quote metrics on a video. */
await kinora.getPlayerVideoSecondaryData(playerProfile, postId);

/* Get the Quest Id from it's Lens Publication id and Kinora Quest Data Address from factory instance. */
await kinora.getQuestIdFromPublication(postId, kinoraQuestDataContractAddress);

/* Get all quests completed by a Player and Kinora Quest Data Address from factory instance. */
await kinora.getPlayerCompletedQuests(playerProfile, kinoraQuestDataContractAddress);

/* Get list of Quests a Player has joined and Kinora Quest Data Address from factory instance. */
await kinora.getPlayerJoinedQuests(playerProfile, kinoraQuestDataContractAddress);

/* Get all Milestones a Player has completed within a quest and Kinora Quest Data Address from factory instance. */
await kinora.getPlayerQuestMilestonesCompleted(playerProfile, questId, kinoraQuestDataContractAddress);

/* Get all Video Metric activity of a Player across all videos they've logged metrics for and Kinora Quest Data Address from factory instance. */
await kinora.getPlayerVideoMetricActivity(playerProfile, kinoraQuestDataContractAddress);

/* Get all details associated with a Player including video and quest history and Kinora Quest Data Address from factory instance. */
await kinora.getPlayerDetails(playerProfile, kinoraQuestDataContractAddress);

/* Get all the players for a quest and Kinora Quest Data Address from factory instance. */
await kinora.getAllQuestPlayers(questId, kinoraQuestDataContractAddress);

/* Get all quests created by an envoker and Kinora Quest Data Address from factory instance. */
await kinora.getQuestsByEnvoker(envokerProfileId, kinoraQuestDataContractAddress);

/* Get all Kinora created quests. */
await kinora.getAllQuests();

/* Get all Kinora video metrics logged by all Players. */
await kinora.getAllVideoMetrics();

/* Get all Quests that include a Livepeer PlaybackId and Kinora Quest Data Address from factory instance. */
await kinora.getQuestsByPlaybackId(playbackId, kinoraQuestDataContractAddress);

/* Get all Quests that include a Lens Video Post and Kinora Quest Data Address from factory instance. */
await kinora.getQuestsByVideoPost(videoPostId, kinoraQuestDataContractAddress);

/* Get Video activity associated with a Livepeer PlaybackId and Kinora Quest Data Address from factory instance. */
await kinora.getVideoActivityByPlaybackId(playbackId, kinoraQuestDataContractAddress);

/* Get Video activity associated with a Video Post Id and Kinora Quest Data Address from factory instance. */
await kinora.getVideoActivityByVideoPost(videoPostId, kinoraQuestDataContractAddress);
```

{% endcode %}


# Quest No Code

kinora.irrevocable.dev

**Keen to venture on quests without the code entanglement?**

Unlock the gateway to a no code implementation of the Quest Dispatch [here](https://kinora.irrevocable.dev).

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2Fuc7HuqJ7v7KnnFg2Aj5Y%2Fsignal-2023-10-21-195336_004.jpeg?alt=media&amp;token=c977698e-4884-444f-8e44-797d0f1a5fa2" alt=""><figcaption></figcaption></figure>


# Cypher Search

cypher.digitalax.xyz

The KinoraSDK is also integrated in the Cypher Search app, search and join quests [here](https://cypher.digitalax.xyz/).

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FJ8m86qjjr9mfrMSaSV2g%2Fimage.png?alt=media&amp;token=b9f8a962-7545-47d5-8bee-9b8a040d697f" alt=""><figcaption></figcaption></figure>


# Chromadin

www\.chromadin.xyz

24/7 Channel Surfing drop generator. Play [here](https://www.chromadin.xyz/).

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FACmMLVF56utIAihbQkx1%2F%20.png?alt=media&amp;token=740234a4-2b92-4967-828e-c342272656cd" alt=""><figcaption></figcaption></figure>


# Data Corruption Mitigation

Off-Chain Videometric Calculation.

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2F8hjDxOaH59NwqEGQwRKP%2FRectangle.png?alt=media&amp;token=0e5a85b5-5661-46b9-adf4-1ec4f9b209ef" alt=""><figcaption></figcaption></figure>

To qualify for Quest Milestone rewards, players are required to first satisfy the outlined Milestone completion conditions, which span both Token Gated directives and videometric specifications.

The Token Gated requisites are authenticated on-chain via the Kinora Smart Contracts, based on a player's wallet holdings, which significantly diminishes the likelihood of undue manipulation by players or envokers.

The Videometric specifications are also cross-checked on-chain within the Kinora Open Action contract. However, because the videometrics are ultimately recorded as off-chain data, through the SDK, there is the potential for fabrication via dummy data by a Quest deployer or more notably, a Player. They could potentially input manipulated data directly into the Kinora Metrics Contract through the `addPlayerMetrics` function.

To preemptively address this,  we've integrated Lens protocol at the core of the architecture, coupled with an exclusive Envoker-Only milestone verification process and Kinora Factory instantiations.&#x20;

Participation in any Quest, either as a player or an envoker, mandates ownership of a Lens Profile. This requirement establishes an additional layer of transparent social accountability. Essentially, any false data produced is irrevocably linked to the perpetrator's public Lens Profile, facilitating straightforward verification through various data points tied to the Lens ecosystem.

Further, before a player can lay claim to Milestone Rewards, their achievements must be validated by the envoker. This is essential, even if the player has already met the videometric and token-gated criteria. The envoker, leveraging the public platform, assesses the player’s credibility. This is done by scrutinizing their Lens network - evaluating aspects like posts, collections, followers, comments, and engagement metrics. It’s not just about reaching milestones; it’s about maintaining a reputable digital footprint.

Moreover, with every Quest Instantiation integrated into its own Factory Suite, envokers can more easily track their metrics' sources and define who interacts with their contracts. This confines issues like spam and false data creation, so envokers can solely focus on validating player video metrics in their suite, bypassing the hassle of overseeing metrics from all suites.

These dual layers of verification – one leveraging the inherent trust mechanics of social networks, and the other instilling a sense of reputational risk – work in tandem. They not only ensure that players engage with Quests authentically but also embed a palpable sense of accountability. Missteps don't just affect game progress; they threaten one's standing in their digital community.&#x20;


# Issues

Reporting Issues and Errors.

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FEQqxWIlu2BylEZgktyYJ%2F00058-60479447.png?alt=media&amp;token=aad80709-2813-42ed-9d93-71fa90398b7e" alt=""><figcaption></figcaption></figure>

If you encounter any issues or errors while using the SDK, you can open a new issue [here](https://github.com/DIGITALAX/KinoraSDK/issues).

When creating a new issue, please provide as much information as possible to help us understand and reproduce the problem. Here are some guidelines to follow:

* Give the issue a clear and concise title that summarizes the problem.&#x20;
* Describe the issue in detail, inclusive of reproduction steps, error messages, and related logs.
* Specify your environment, including the versions of the SDK, Node.js and any other relevant software or packages you are using.&#x20;
* Include screenshots if possible.


# Testing

Running the Test Suite.

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FqDLFUoQ4c6AASGflXDLS%2F00982-657275145.png?alt=media&amp;token=e1964d44-3753-4fc9-bbd3-c4f92e314659" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
The test suite was migrated to Forge upon migration to LensV3. Not all tests are included on Github yet. The following provides instructions for running the LensV2 test suite.
{% endhint %}

A comprehensive Test Suite for the SDK and Contract Suite is located in the `test` folder in the root of the project. Before running the tests, you will need to compile the project and set up your environment variables.

### **Kinora Contract Suite and SDK Test**

{% hint style="warning" %}
comment out this part of the **KinoraOpenAction** contract before running the test suite or an error will throw. It is only a required check once deployed.

```solidity
!MODULE_GLOBALS.isErc20CurrencyRegistered(
       _params.milestones[i].rewards[j].tokenAddress
          )
  ) {
    revert KinoraErrors.CurrencyNotWhitelisted();
 }
```

{% endhint %}

{% hint style="info" %}
The SDK is being continuously updated to include additional features and configurations, if a contract test is not passing, it could be due to a discrepency in the test suite and the latest SDK version. Please let us know if you find anything!
{% endhint %}

To run the contract and node Sequence tests, place a valid `PRIVATE_KEY` in your root `.env` file to create the signer object for transacting within the hardhat local network.

```sh
PRIVATE_KEY=
```

Run the following in your command line:

```bash
npm run test
```


# Contributors

Contributing to the SDK.

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FlCPcxtqhhiozo63DBpvC%2F00769-1488869390.png?alt=media&amp;token=a30956db-8919-4fd8-bcf8-0bb67d6a6a85" alt=""><figcaption></figcaption></figure>

### Forge a new path on your Quest.

1. Clone the forked repository.

```bash
git clone https://github.com/DIGITALAX/KinoraSDK
```

2. Create a new branch.

```bash
git checkout -b <branch_name>
```

3. Commit code additions locally.

```bash
git add .
git commit -m "Description of the changes"
```

4. Push changes to the forked repository.

```bash
git push origin <branch_name>
```

5. Create a pull request on the KinoraSDK repository.

```bash
git checkout <branch_name>
git pull upstream main
```


# About the Devs

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2FjQhlDqXkFinUYksCQaCx%2F00851-2224248561.png?alt=media&amp;token=ab7d8ccb-29f7-4716-aa92-62016513a3e3" alt=""><figcaption></figcaption></figure>

Brought to you by the core devs at [DIGITALAX](https://www.digitalax.xyz/).


# Storefront

kinora.irrevocable.dev/storefront

**Quests for fashion too?**&#x20;

Custom Kinora Print Looks to collect, wear and [level up](https://kinora.irrevocable.dev/storefront).

<figure><img src="https://904340753-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKxchJQ9hoNG85dYRcdgl%2Fuploads%2F36MmAOKU2ondSZzuoI2G%2Fasd.png?alt=media&amp;token=c1070894-c8ad-43f1-b352-c883c289b712" alt=""><figcaption></figcaption></figure>


