npm package: @rotateprotocol/sdk
Description: Rotate Protocol SDK - The easiest way to accept payments on Solana. Non-custodial, instant settlement.
Author: Rotate Protocol
Latest version: 1.0.2

<p align="center">
  <img src="../assets/rotate-banner-final.png" alt="Rotate Protocol" width="600">
</p>

# @rotateprotocol/sdk

**Accept Solana payments in minutes. Non-custodial, instant settlement.**

## Install

```bash
npm install @rotateprotocol/sdk
```

---

## Quick Start (React)

```tsx
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)

```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

```typescript
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

```typescript
// 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

```typescript
// 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

```tsx
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

```tsx
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

```tsx
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

```tsx
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

```html
<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

```typescript
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

```typescript
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:

```typescript
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

```typescript
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 vendor
```

### Store React Hooks

```tsx
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](https://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

```bash
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:

```bash
# Terminal 1: Start local validator
solana-test-validator

# Terminal 2: Run tests (integration tests will connect to localhost:8899)
cd sdk
npm test
```

If no local validator is running, integration tests fall back to devnet.

### Running Anchor Tests

```bash
cd solana
yarn install
anchor test       # Builds, deploys to localnet, and runs tests
```

### Environment Variables

Create a `.env` file in the SDK directory for custom configuration:

```bash
# Custom RPC endpoint (optional, defaults to localhost:8899 for tests)
RPC_URL=http://127.0.0.1:8899
```

---

## License

MIT