@rotateprotocol/sdk
v1.0.2
Published
Rotate Protocol SDK - The easiest way to accept payments on Solana. Non-custodial, instant settlement.
Maintainers
Readme
@rotateprotocol/sdk
Accept Solana payments in minutes. Non-custodial, instant settlement.
Install
npm install @rotateprotocol/sdkQuick Start (React)
import { RotateProvider, PaymentButton } from '@rotateprotocol/sdk/components';
function App() {
return (
<RotateProvider merchantId={1000000} platformId={1000000}>
<PaymentButton
amount={29.99}
onSuccess={(payment) => console.log('Paid!', payment)}
/>
</RotateProvider>
);
}Quick Start (HTML)
<script src="https://js.rotatepay.io/embed.js"></script>
<button
data-rotate-pay
data-amount="29.99"
data-merchant="1000000"
data-platform="1000000"
>
Pay $29.99
</button>Creating Platforms & Merchants
import RotateSDK from '@rotateprotocol/sdk';
const sdk = new RotateSDK({ network: 'mainnet-beta' });
sdk.initWithWallet(wallet);
// Create platform (one-time) — ID assigned automatically
const { platformId } = await sdk.createPlatform({
feeBps: 300, // 3% platform fee
wallet: wallet.publicKey,
});
// Create merchant (one-time)
const { merchantId } = await sdk.createMerchant({
platformId,
wallet: wallet.publicKey,
});Creating Payment Links
// USD link — buyer chooses SOL, USDC, or USDT
const { linkId } = await sdk.createLinkUsd({
merchantId, platformId,
amount: 25_000_000n, // $25.00 (micro-USD, 6 decimals)
allowTips: true,
});
// SOL link
const { linkId } = await sdk.createLinkSol({
merchantId, platformId,
amount: 500_000_000n, // 0.5 SOL (lamports, 9 decimals)
});
// USDC/USDT link
const { linkId } = await sdk.createLinkToken({
merchantId, platformId,
amount: 25_000_000n, // $25.00
currency: 'USDC', // or 'USDT'
});Link Types
| Type | Method | Amount Unit | Buyer Pays With |
|------|--------|-------------|-----------------|
| USD | createLinkUsd | Micro-USD (6 dec) | SOL, USDC, or USDT |
| SOL | createLinkSol | Lamports (9 dec) | SOL only |
| USDC | createLinkToken | Micro-USDC (6 dec) | USDC only |
| USDT | createLinkToken | Micro-USDT (6 dec) | USDT only |
Tips
Set allowTips: true when creating a link. Tips go 100% to the merchant — no fees on tips. Capped at 100% of the payment amount.
Expiration
E-commerce links auto-expire after 5 minutes. Dashboard links can be manually cancelled.
Checking Payment Status
// Get link status
const link = await sdk.getPaymentLink(linkId);
console.log(link.status); // 'Pending' | 'Paid' | 'Cancelled'
// Check if paid
const isPaid = await sdk.isLinkPaid(linkId);
// Wait for payment (with timeout)
const result = await sdk.waitForPayment(linkId, 300_000); // 5 min
// Get checkout URL
const url = sdk.getPaymentUrl(linkId);React Components
PaymentButton
import { PaymentButton } from '@rotateprotocol/sdk/components';
<PaymentButton
amount={99.99}
currency="USD"
label="Buy Now"
variant="solid" // 'solid' | 'outline' | 'ghost'
size="lg" // 'sm' | 'md' | 'lg'
allowTips={true}
onSuccess={(payment) => router.push('/success')}
onError={(error) => alert(error.message)}
/>CheckoutForm
import { CheckoutForm } from '@rotateprotocol/sdk/components';
<CheckoutForm
amount={149.99}
productName="Pro Plan"
productDescription="Unlimited access for 1 year"
allowTips={true}
showCurrencySelector={true}
onSuccess={(payment) => fulfillOrder(payment.linkId)}
/>HostedCheckout
import { HostedCheckout } from '@rotateprotocol/sdk/components';
<HostedCheckout
linkId={12345}
merchantId={1000000}
platformId={1000000}
brandName="My Store"
brandColor="#8B5CF6"
onSuccess={(result) => window.location.href = '/success'}
/>React Hooks
import { usePaymentLink, useSolPrice, useRotate, useCreateEntities, useTokenBalances } from '@rotateprotocol/sdk/react';
// Payment status
const { link, status, startPolling, stopPolling } = usePaymentLink(linkId);
// SOL price
const { price, usdToSol, solToUsd } = useSolPrice();
// SDK instance
const { sdk, connected, publicKey } = useRotate({ network: 'mainnet-beta' });
// Create entities
const { createPlatform, createMerchant, creating } = useCreateEntities();
// Token balances
const { balances, hasEnough } = useTokenBalances();Vanilla JavaScript
<script src="https://js.rotatepay.io/embed.js"></script>
<script>
Rotate.configure({ network: 'mainnet-beta' });
// Open checkout popup
Rotate.checkout({
amount: 29.99,
merchantId: 1000000,
platformId: 1000000,
allowTips: true,
onSuccess: (payment) => console.log('Paid!', payment),
});
</script>Data Attributes
| Attribute | Description | Example |
|-----------|-------------|---------|
| data-rotate-pay | Marks as payment button | (no value) |
| data-amount | Amount | "29.99" |
| data-merchant | Merchant ID | "1000000" |
| data-platform | Platform ID | "1000000" |
| data-currency | Currency | "USD", "SOL" |
| data-tips | Allow tips | "true" |
| data-brand | Brand name | "My Store" |
| data-color | Brand color | "8B5CF6" |
Webhooks
import { EventListener, verifyWebhookSignature } from '@rotateprotocol/sdk/webhooks';
const listener = new EventListener({
network: 'mainnet-beta',
merchantId: 1000000,
});
listener.on('payment.completed', async (event) => {
await fulfillOrder(event.data.orderRef);
});
listener.start();Server-Side Verification
app.post('/api/webhooks/rotate', async (req, res) => {
const signature = req.headers['x-rotate-signature'];
const isValid = await verifyWebhookSignature(
JSON.stringify(req.body), signature, process.env.WEBHOOK_SECRET
);
if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
if (req.body.type === 'payment.completed') {
await fulfillOrder(req.body.data.orderRef);
}
res.json({ received: true });
});Events
| Event | When |
|-------|------|
| payment.completed | Payment successful |
| payment.pending | Payment started |
| link.created | New link created |
| link.cancelled | Link cancelled |
| link.expired | Link expired |
Platform Merchant Onboarding
For marketplaces that need to onboard sellers:
import { RotatePlatformManager } from '@rotateprotocol/sdk/platform';
const manager = new RotatePlatformManager(sdk, { platformId: 1000000 });
// Onboard a seller
const { merchantId } = await manager.onboardMerchant({
wallet: sellerAddress,
name: 'Coffee Shop',
metadata: { location: 'NYC' },
});
// Bulk onboard
const results = await manager.onboardMerchants(sellers, {
onProgress: (i, total) => console.log(`${i}/${total}`),
});
// Stats
const stats = await manager.getStats();
// Export/import directory for persistence
const data = manager.exportDirectory();
manager.importDirectory(data);Only the merchant's wallet owner can update their account. Platform admins cannot modify merchant settings after creation.
Store & Marketplace
import { RotateStore } from '@rotateprotocol/sdk/store';
import { RotateMarketplace } from '@rotateprotocol/sdk/marketplace';
// Single-vendor store
const store = new RotateStore(sdk, { merchantId: 1000000, platformId: 1000000 });
store.addProducts([{ id: 'tshirt', name: 'Logo Tee', price: 29.99, inventory: 100 }]);
store.addDiscount({ code: 'SAVE10', type: 'percent', value: 10 });
const cart = store.createCart();
cart.addItem('tshirt', 2);
cart.applyDiscount('SAVE10');
const { paymentUrl } = await cart.checkout();
// Multi-vendor marketplace
const marketplace = new RotateMarketplace(sdk, { platformId: 1000000, platformFeeBps: 200 });
marketplace.addVendor({ id: 'vendor-a', merchantId: 1000001, name: 'Shirt Co.' });
marketplace.addProduct({ id: 'shirt-1', vendorId: 'vendor-a', name: 'Logo Tee', price: 29.99 });
const mktCart = marketplace.createCart();
mktCart.addItem('shirt-1', 2);
const result = await mktCart.checkout(); // Creates separate links per vendorStore React Hooks
import { useProducts, useCart } from '@rotateprotocol/sdk/hooks';
const { products } = useProducts(store);
const { addItem, totals, checkout } = useCart(store);Fees
| Fee | Rate | |-----|------| | Protocol | 3% (fixed) | | Platform | 0–6% (you set it) | | Split | 50/50 buyer/seller | | Tips | 100% to merchant |
API Reference
RotateSDK Methods
| Method | Description |
|--------|-------------|
| getPaymentLink(id) | Get link status |
| isLinkPaid(id) | Check if paid |
| waitForPayment(id, timeout) | Wait for payment |
| createPlatform(params) | Create platform |
| createMerchant(params) | Create merchant |
| createLinkUsd(params) | Create USD link |
| createLinkSol(params) | Create SOL link |
| createLinkToken(params) | Create USDC/USDT link |
| payLinkSol(params) | Pay SOL link |
| payLinkToken(params) | Pay token link |
| payLinkUsdSol(params) | Pay USD link with SOL |
| payLinkUsdToken(params) | Pay USD link with USDC/USDT |
| cancelLink(linkId, merchantId) | Cancel pending link |
| getPaymentUrl(id) | Get checkout URL |
| getSolPrice() | Get SOL/USD price |
| calculateFees(amount, platformFeeBps) | Calculate fee breakdown |
ID System
| Entity | Format | Example |
|--------|--------|---------|
| Platform | 7-digit sequential | #1000000 |
| Merchant | 7-digit sequential | #1000000 |
| Payment Link | Sequential | #1, #2, #3 |
Development & Testing
Prerequisites
| Tool | Version | Install |
|------|---------|---------|
| Node.js | 20+ | nodejs.org |
| Solana CLI | 2.3.5 | sh -c "$(curl -sSfL https://release.anza.xyz/v2.3.5/install)" |
| Anchor CLI | 0.32.1 | cargo install --git https://github.com/coral-xyz/anchor avm && avm install 0.32.1 && avm use 0.32.1 |
| Rust | stable | rustup update stable |
Running SDK Tests
cd sdk
npm install
npm test # Unit tests (no validator needed)Running Integration Tests (Local Validator)
Integration tests automatically detect a local validator. To start one:
# Terminal 1: Start local validator
solana-test-validator
# Terminal 2: Run tests (integration tests will connect to localhost:8899)
cd sdk
npm testIf no local validator is running, integration tests fall back to devnet.
Running Anchor Tests
cd solana
yarn install
anchor test # Builds, deploys to localnet, and runs testsEnvironment Variables
Create a .env file in the SDK directory for custom configuration:
# Custom RPC endpoint (optional, defaults to localhost:8899 for tests)
RPC_URL=http://127.0.0.1:8899License
MIT
