npm package: @spherelabs/lite-sdk
Description: Lite Typescript SDK for Sphere
Author: sphere-labs
Latest version: 1.3.12

<div align="center">
    <a>
        <img alt="Sphere" src="https://avatars.githubusercontent.com/u/109333730?s=200&v=4" width="110"/>
    </a>
  <h1 style="margin-top:20px;">Sphere Lite SDK </h1>

  <p>
    <a href="https://docs.sphere.engineer/docs/gettingstarted_introduction"><img alt="Docs" src="https://img.shields.io/badge/docs-tutorials-aquamarine" /></a>
    <!-- <a href="https://discord.com/channels/849494028176588802/878700556904980500"><img alt="Discord Chat" src="https://img.shields.io/discord/889577356681945098?color=aquamarine" /></a> -->
    <a href="https://opensource.org/licenses/MIT"><img alt="License" src="https://img.shields.io/github/license/git-scm/git-scm.com?color=aquamarine" /></a>
  </p>
</div>

# Sphere Protocol

This repository contains the open source code for Sphere's Lite Typescript SDK.

# Documentation

## Overview

This SDK enables you to create one-time payments for unit/credit based products, with the option of adding different pricing tiers (e.g., Basic, Developer, Enterprise), and the ability to pay in any currency.

There are additional utility functions to fetch quotes and create webhooks.

We also support both 'volume' and 'graduated' billing methods, if necessary. This [blog post](https://blog.fusebill.com/tiered-vs-volume-pricing-difference) explains what these mean.

## Installation

```bash
npm i @spherelabs/lite-sdk
```

## Integration

At a high-level, developers will:

1. Create a product to describe the goods and services they are selling: including the cost, billing method, and pricing tiers if relevant.
2. Create a button on your frontend to allow your customer to purchase your products.
3. Create webhooks to receive payment events for your products.

## Integration Guide

We will demonstrate the step-by-step flow for embedding the payments logic into a button.

### 1. Setup

Create a script with the following:

```typescript
import { Sphere } from '@spherelabs/lite-sdk';

const sphere = new Sphere({
      env: 'mainnet',
      rpcUrl: 'https://api.mainnet-beta.solana.com',
      signer: '3iJjsyYEDDhkvTNsnwJcVapXEv82Qqbh5BSCjZcamwwFLC9Gdw5rurQxtomLCEjwGHbWYk1p7yoihbQQGXeXsk1p',
});

var product = await sphere.product.create({
      name: 'Service Credits',
      description: 'A product representing credits for your service',
      meta: {},
      images: ['http://localhost:8080/img1.png'],
      currency: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
      taxRate: 0.01,
      billingMode: 'graduated',
      tiers: [
            {
                  units: 1,
                  price: 0.5,
            },
            {
                  units: 3,
                  price: 0.25,
            },
            {
                  units: 5,
                  price: 0.1,
            },
      ],
});
console.log(product.id);
```

Keep a note of your product's `id`. You will need to put this into your frontend.

### 2. Frontend

In your React App, create the following "product purchase" `Button`:

```typescript
import { useEffect, useState } from 'react';
import { Sphere } from '@spherelabs/lite-sdk';
import { useWallet } from '@solana/wallet-adapter-react';

export const Button = ({ units, id }: any) => {
      const reactWallet = useWallet();
      const [sphere, setSphere] = useState<Sphere>();
      const [txSig, setTxSig] = useState<string>('');

      useEffect(() => {
            (async () => {
                  if (reactWallet && reactWallet.connected) {
                        try {
                              const _sphere = new Sphere({
                                    env: 'mainnet',
                                    rpcUrl: 'https://solana-mainnet.g.alchemy.com/v2/UesFjy66kB4g4BEa1-JFNCEYIP_FxXvF',
                                    reactWallet,
                              });
                              setSphere(_sphere);
                              await _sphere.prepareFrontend();
                        } catch (error) {
                              console.log(error);
                        }
                  }
            })();
      }, [reactWallet.connected]);

      const onClick = async () => {
            try {
                  console.log(sphere);
                  const _txSig = (await sphere?.product.purchase({
                        id,
                        units,
                  })) as string;
                  setTxSig(_txSig);
            } catch (error) {
                  console.log(error);
            }
      };

      return (
            <>
                  {txSig ? (
                        <p>
                              transactionSignature: &nbsp;
                              <a
                                    href={`https://solscan.io/tx/${txSig}`}
                                    style={{
                                          color: 'blue',
                                          textDecoration: 'underline',
                                    }}
                              >
                                    {txSig}
                              </a>
                        </p>
                  ) : (
                        <button className="buttonPurchase" onClick={onClick}>
                              Purchase Product
                        </button>
                  )}
            </>
      );
};
```

Now, you can put your button somewhere in your application:

```typescript
<Button id="product-4d081945-7dab-4b15-8cb2-632ae5818a2f" units={2} />
```

Users can click this button to purchase the product at a specified amount of units.

We have included a full [NextJS React App](https://github.com/Sphere-Laboratories/sphere-lite-client) to demonstrate this.

### 3. Events

Sphere generates and stores purchase events every time your customers purchases a product. 

#### Event Payloads

The following is an example event body payload for product purchases with SPL tokens:

```json
{
      "id": "event-a5c415c5-1d90-4dfe-99ee-c81538d61d2c", 
      "product": "product-955d6030-f3e3-4aa1-a2cd-87eaab91a7c0", 
      "transferType": "spl", 
      "customer": "6VxGFK7NTv8tmRS2NvMNGAdqwkN5Q1b4X2qEk71Uf8N2", 
      "merchant": "AEHY6aV66YoDMUbhnuXGPANyikooaQgm8spFAJro9AFV", 
      "customerTokenAccount": "AxFuniPo7RaDgPH6Gizf4GZmLQFc4M5ipckeeZfkrPNn", 
      "merchantTokenAccount": "34NsPzjUUMcnG8KAsuPhg6EqeLkmYTpJhpbUb9SRaBiJ", 
      "currency": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 
      "amount": 10, 
      "fee": 0.03, 
      "units": 10, 
      "fromCurrency": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
      "txSig": "3ozMCwVProD2iksCAMNnfzdN5jGveEYfGjzKZw3UQmZQFPgyt39w2FuN2PPvtgbV2bzpanzss4zonPjKAbdy8etk",
      "created": "2023-02-08T02:51:54.405Z"
}
```

The following is an example event body payload for product purchases with native SOL:


```json
{
      "id": "event-a5c415c5-1d90-4dfe-99ee-c81538d61d2c", 
      "product": "product-955d6030-f3e3-4aa1-a2cd-87eaab91a7c0", 
      "transferType": "sol", 
      "customer": "6VxGFK7NTv8tmRS2NvMNGAdqwkN5Q1b4X2qEk71Uf8N2", 
      "merchant": "AEHY6aV66YoDMUbhnuXGPANyikooaQgm8spFAJro9AFV", 
      "customerTokenAccount": "", 
      "merchantTokenAccount": "", 
      "currency": "So11111111111111111111111111111111111111111", 
      "amount": 10, 
      "fee": 0.03, 
      "units": 10, 
      "fromCurrency": "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R",
      "txSig": "3ozMCwVProD2iksCAMNnfzdN5jGveEYfGjzKZw3UQmZQFPgyt39w2FuN2PPvtgbV2bzpanzss4zonPjKAbdy8etk",
      "created": "2023-02-08T02:51:54.405Z"
}
```

#### Retrieving Events


We have included several functions to retrieve your events. 

To get all ids for your events, execute:

```typescript
const ids: string[] = await sphere.event.getIds()
```

To get all events associated to a given customer's publickey: 

```typescript
const events = await sphere.event.listByCustomer("OPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
```
To get all events associated to your infrastructure:

```typescript
const events = await sphere.event.list()
```


### 4. Webhooks

To propagate purchase events to your API, you may use webhooks. To create a webhook execute the following:

```typescript
var webhook = await sphere.webhook.create({
      name: 'Example Product Payments Webhook',
      description: 'Ingests product payment events',
      topics: ['sphere.events.product.purchase'],
      url: 'http://localhost:3000',
});
```

We support events of a single topic: `sphere.events.product.purchase`. These events are emitted on purchase of a product by one of your customers.

Events will be sent to your API at most 30 seconds after they have been confirmed onchain. If your API is unavaliable Sphere will attempt to deliver events 10 times with 1 second backoff period.

You may only create <b>one</b> webhook per signer. That is to say that, each merchant has at most 1 associated webhook.

#### Signing Secrets

Webhooks contain a signing secret which you may use to authenticate requests.

```typescript
const signingSecret: string = webhook.secret;
```

POST requests made to your API will contain this secret in the `Authorization` field of the request headers.


You may want to alter or disable your webhook when not in-use. To update your webhook:

```typescript
var webhook = await sphere.webhook.update({
      name: 'Example Product Payments Webhook',
      description: 'Ingests product payment events',
      topics: ['sphere.events.product.purchase'],
      active: false,
      url: 'http://localhost:3000',
});
```

#### 5. Development

To test your webhooks locally we recommend using a reverse proxy such as [ngrok](https://ngrok.com) to create a secure tunnel between a publically exposed endpoint and your API.

## Swaps

We support Token Swaps via [Jupiter](https://jup.ag/) for product purchases. Hence, your customers can pay in the currency of their choice.

To include a swap in a product purchase, select the `fromCurrency` that the customer is paying in, which they can specify on your frontend. We should note that the merchant will receive the `currency` originally specified on creation of the product.

```typescript
const txSig = await sphere.product.purchase({
      id: product.id,
      units: 2,
      swap: {
            fromCurrency: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
      },
});
```

When using the swap feature, you may wish to display price quotes to your users given the price structure of your product (e.g., the number of units they want to buy and the currency they wish to pay in). To get a quote in the `fromCurrency` for a given amount of units:

```typescript
const quote = await sphere.product.quote({
      id: product.id,
      fromCurrency: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
      units: 5,
});
```

## Billing Modes

### Graduated

Consider the following graduated tier pricing structure:
<br/>
Units [0-100] have a price per unit of 1
<br/>
Units [101-250] have a price per unit of 2
<br/>
Units [251+] have a price per unit of 3
<br/>

So if the customer uses 300 units, the total will be: 100(1) + 150(2) + 50(3) = 550.

This tier structure will have the following array encoding:

```typescript
const tiers = [
      {
            price: 1,
            units: 100,
      },
      {
            price: 2,
            units: 150,
      },
      {
            price: 3,
            units: 250,
      },
];
```

### Volume

Consider the following volume tiered pricing structure:
<br/>
Units [0-100] have a price per unit of 1
<br/>
Units [101-250] have a price per unit of 2
<br/>
Units [251+] have a price per unit of 3
<br/>

So if the customer uses 300 units, the total will be: 3(300) = 900. Alternatively, if they use 50 units, the total will be 50(1) = 50.

This tier structure will have the following array encoding:

```typescript
const tiers = [
      {
            price: 1,
            units: 0,
      },
      {
            price: 2,
            units: 100,
      },
      {
            price: 3,
            units: 250,
      },
];
```

Notice that for volume based billing, the units specified indicate the point at which the given price kicks in (e.g., after the first 100 units, the price becomes 2 instead of 1). This is unlike graduated billing, where the units specify the amount of units that can be consumed at a given price point (e.g., the first 100 units are allocated the price 1, and the next 150 are allocated price 2). In other words, **graduated billing defines units by an upper limit per tier,** whereas **volume based billing defines units by a lower bound per tier.** The reason for this difference is because in volume based billing, the pricing is set by the most recent tier. Hence, without using a lower limit, it would be unclear how many units to specify for the final tier.

To illustrate, if volume based billing used the same upper limit method as graduated:

```typescript
const tiers = [
  {
      price: 1,
      units: 100
  },
  {
      price: 2,
      units: 250
  },
  {
      price: 3,
      units: ?
  },
]
```

Hence, the last tier's units would be undefined. This is why we use a lower limit for this billing method.

## Fees

Sphere charges a fee of <b>0.3%</b> for all transfers using our infrastructure.

## License

Sphere Protocol is licensed under [MIT LICENSE](./LICENSE).

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in Sphere SDK by you, as defined in the MIT license, shall be
licensed as above, without any additional terms or conditions.