1.3.12 • Published 1 year ago

@spherelabs/lite-sdk v1.3.12

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

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 explains what these mean.

Installation

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:

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:

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:

<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 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:

{
      "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:

{
      "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:

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

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

const events = await sphere.event.listByCustomer("OPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")

To get all events associated to your infrastructure:

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:

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 one 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.

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:

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 to create a secure tunnel between a publically exposed endpoint and your API.

Swaps

We support Token Swaps via Jupiter 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.

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:

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

Billing Modes

Graduated

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

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:

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

Volume

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

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:

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:

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 0.3% for all transfers using our infrastructure.

License

Sphere Protocol is licensed under MIT 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.

1.3.11

1 year ago

1.3.12

1 year ago

1.3.10

1 year ago

1.3.9

1 year ago

1.3.8

1 year ago

1.3.7

1 year ago

1.3.6

1 year ago

1.3.5

1 year ago

1.3.4

1 year ago

1.3.3

1 year ago

1.3.2

1 year ago

1.3.1

1 year ago

1.3.0

1 year ago

1.2.4

1 year ago

1.2.3

1 year ago

1.2.2

1 year ago

1.2.1

1 year ago

1.1.0

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

0.1.0

1 year ago

0.0.17

1 year ago

0.0.16

1 year ago

0.0.15

1 year ago

0.0.14

1 year ago

0.0.13

1 year ago

0.0.12

1 year ago

0.0.11

1 year ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.7

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago