@spherelabs/lite-sdk
v1.3.12
Published
Lite Typescript SDK for Sphere
Downloads
1,396
Readme
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:
- Create a product to describe the goods and services they are selling: including the cost, billing method, and pricing tiers if relevant.
- Create a button on your frontend to allow your customer to purchase your products.
- 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:
<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.