@rotateprotocol/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +453 -0
  2. package/dist/catalog.d.ts +112 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +210 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/components/CheckoutForm.d.ts +86 -0
  7. package/dist/components/CheckoutForm.d.ts.map +1 -0
  8. package/dist/components/CheckoutForm.js +332 -0
  9. package/dist/components/CheckoutForm.js.map +1 -0
  10. package/dist/components/HostedCheckout.d.ts +57 -0
  11. package/dist/components/HostedCheckout.d.ts.map +1 -0
  12. package/dist/components/HostedCheckout.js +414 -0
  13. package/dist/components/HostedCheckout.js.map +1 -0
  14. package/dist/components/PaymentButton.d.ts +80 -0
  15. package/dist/components/PaymentButton.d.ts.map +1 -0
  16. package/dist/components/PaymentButton.js +210 -0
  17. package/dist/components/PaymentButton.js.map +1 -0
  18. package/dist/components/RotateProvider.d.ts +115 -0
  19. package/dist/components/RotateProvider.d.ts.map +1 -0
  20. package/dist/components/RotateProvider.js +264 -0
  21. package/dist/components/RotateProvider.js.map +1 -0
  22. package/dist/components/index.d.ts +17 -0
  23. package/dist/components/index.d.ts.map +1 -0
  24. package/dist/components/index.js +27 -0
  25. package/dist/components/index.js.map +1 -0
  26. package/dist/embed.d.ts +85 -0
  27. package/dist/embed.d.ts.map +1 -0
  28. package/dist/embed.js +313 -0
  29. package/dist/embed.js.map +1 -0
  30. package/dist/hooks.d.ts +156 -0
  31. package/dist/hooks.d.ts.map +1 -0
  32. package/dist/hooks.js +280 -0
  33. package/dist/hooks.js.map +1 -0
  34. package/dist/idl/rotate_connect.json +2572 -0
  35. package/dist/index.d.ts +505 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1197 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/marketplace.d.ts +257 -0
  40. package/dist/marketplace.d.ts.map +1 -0
  41. package/dist/marketplace.js +433 -0
  42. package/dist/marketplace.js.map +1 -0
  43. package/dist/platform.d.ts +234 -0
  44. package/dist/platform.d.ts.map +1 -0
  45. package/dist/platform.js +268 -0
  46. package/dist/platform.js.map +1 -0
  47. package/dist/react.d.ts +140 -0
  48. package/dist/react.d.ts.map +1 -0
  49. package/dist/react.js +429 -0
  50. package/dist/react.js.map +1 -0
  51. package/dist/store.d.ts +213 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +404 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/webhooks.d.ts +149 -0
  56. package/dist/webhooks.d.ts.map +1 -0
  57. package/dist/webhooks.js +371 -0
  58. package/dist/webhooks.js.map +1 -0
  59. package/package.json +114 -0
  60. package/src/catalog.ts +299 -0
  61. package/src/components/CheckoutForm.tsx +608 -0
  62. package/src/components/HostedCheckout.tsx +675 -0
  63. package/src/components/PaymentButton.tsx +348 -0
  64. package/src/components/RotateProvider.tsx +370 -0
  65. package/src/components/index.ts +26 -0
  66. package/src/embed.ts +408 -0
  67. package/src/hooks.ts +518 -0
  68. package/src/idl/rotate_connect.json +2572 -0
  69. package/src/index.ts +1538 -0
  70. package/src/marketplace.ts +642 -0
  71. package/src/platform.ts +403 -0
  72. package/src/react.ts +459 -0
  73. package/src/store.ts +577 -0
  74. package/src/webhooks.ts +506 -0
package/src/hooks.ts ADDED
@@ -0,0 +1,518 @@
1
+ /**
2
+ * Rotate React Hooks — Store, Cart & Marketplace
3
+ *
4
+ * Drop-in React hooks for building storefronts and marketplaces.
5
+ * Think of these as a ready-made React integration for Solana payments.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * function ProductPage() {
10
+ * const { products, getByCategory } = useProducts(store);
11
+ * const { cart, addItem, totals, checkout } = useCart(store);
12
+ *
13
+ * return (
14
+ * <div>
15
+ * {getByCategory('shirts').map(p => (
16
+ * <button key={p.id} onClick={() => addItem(p.id)}>
17
+ * {p.name} — ${p.price}
18
+ * </button>
19
+ * ))}
20
+ * <p>Cart: {totals.totalQuantity} items, ${totals.total}</p>
21
+ * <button onClick={() => checkout()}>Pay Now</button>
22
+ * </div>
23
+ * );
24
+ * }
25
+ * ```
26
+ *
27
+ * @packageDocumentation
28
+ */
29
+
30
+ import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
31
+ import {
32
+ RotateStore,
33
+ RotateCart,
34
+ Product,
35
+ LineItem,
36
+ CartTotals,
37
+ CheckoutResult,
38
+ Currency,
39
+ } from './store';
40
+ import {
41
+ RotateMarketplace,
42
+ MarketplaceCart,
43
+ MarketplaceProduct,
44
+ MarketplaceLineItem,
45
+ MarketplaceCartTotals,
46
+ MarketplaceCheckoutResult,
47
+ Vendor,
48
+ } from './marketplace';
49
+
50
+ // ==================== useProducts ====================
51
+
52
+ export interface UseProductsReturn {
53
+ /** All active products */
54
+ products: Product[];
55
+ /** All categories */
56
+ categories: string[];
57
+ /** Get product by ID */
58
+ getProduct: (id: string) => Product | undefined;
59
+ /** Get products by category */
60
+ getByCategory: (category: string) => Product[];
61
+ /** Search products */
62
+ search: (query: string) => Product[];
63
+ /** Paginated product listing */
64
+ paginate: (page: number, perPage: number) => { products: Product[]; totalPages: number; total: number };
65
+ /** Check stock */
66
+ isInStock: (productId: string, qty?: number) => boolean;
67
+ /** Total product count */
68
+ count: number;
69
+ }
70
+
71
+ /**
72
+ * Hook for browsing products in a RotateStore.
73
+ */
74
+ export function useProducts(store: RotateStore): UseProductsReturn {
75
+ // Force re-render on catalog changes
76
+ const [version, setVersion] = useState(0);
77
+ const refresh = useCallback(() => setVersion((v) => v + 1), []);
78
+
79
+ const products = useMemo(
80
+ () => store.getProducts({ active: true }),
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps
82
+ [store, version]
83
+ );
84
+
85
+ const categories = useMemo(
86
+ () => store.getCategories(),
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ [store, version]
89
+ );
90
+
91
+ const getProduct = useCallback((id: string) => store.getProduct(id), [store]);
92
+
93
+ const getByCategory = useCallback(
94
+ (category: string) => store.getProducts({ category, active: true }),
95
+ // eslint-disable-next-line react-hooks/exhaustive-deps
96
+ [store, version]
97
+ );
98
+
99
+ const search = useCallback(
100
+ (query: string) => store.getProducts({ search: query, active: true }),
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ [store, version]
103
+ );
104
+
105
+ const paginate = useCallback(
106
+ (page: number, perPage: number) => {
107
+ const all = store.getProducts({ active: true });
108
+ const total = all.length;
109
+ const totalPages = Math.ceil(total / perPage);
110
+ const offset = (page - 1) * perPage;
111
+ return {
112
+ products: all.slice(offset, offset + perPage),
113
+ totalPages,
114
+ total,
115
+ };
116
+ },
117
+ // eslint-disable-next-line react-hooks/exhaustive-deps
118
+ [store, version]
119
+ );
120
+
121
+ const isInStock = useCallback(
122
+ (productId: string, qty: number = 1) => store.isInStock(productId, qty),
123
+ [store]
124
+ );
125
+
126
+ return {
127
+ products,
128
+ categories,
129
+ getProduct,
130
+ getByCategory,
131
+ search,
132
+ paginate,
133
+ isInStock,
134
+ count: products.length,
135
+ };
136
+ }
137
+
138
+ // ==================== useCart ====================
139
+
140
+ export interface UseCartReturn {
141
+ /** The underlying cart instance */
142
+ cart: RotateCart;
143
+ /** Current line items */
144
+ items: LineItem[];
145
+ /** Calculated totals */
146
+ totals: CartTotals;
147
+ /** Is the cart empty */
148
+ isEmpty: boolean;
149
+ /** Add item to cart */
150
+ addItem: (productId: string, quantity?: number) => void;
151
+ /** Set exact quantity */
152
+ setQuantity: (productId: string, quantity: number) => void;
153
+ /** Remove item */
154
+ removeItem: (productId: string) => void;
155
+ /** Clear entire cart */
156
+ clear: () => void;
157
+ /** Apply discount code */
158
+ applyDiscount: (code: string) => boolean;
159
+ /** Remove discount */
160
+ removeDiscount: (code: string) => boolean;
161
+ /** Set metadata */
162
+ setMetadata: (key: string, value: string) => void;
163
+ /** Checkout and create on-chain payment link */
164
+ checkout: (options?: {
165
+ currency?: Currency;
166
+ expiresIn?: number;
167
+ allowTips?: boolean;
168
+ orderRef?: string;
169
+ }) => Promise<CheckoutResult>;
170
+ /** Whether checkout is in progress */
171
+ checkingOut: boolean;
172
+ /** Last checkout result */
173
+ lastCheckout: CheckoutResult | null;
174
+ /** Error from last operation */
175
+ error: string | null;
176
+ }
177
+
178
+ /**
179
+ * Hook for managing a shopping cart backed by a RotateStore.
180
+ */
181
+ export function useCart(store: RotateStore): UseCartReturn {
182
+ const cartRef = useRef<RotateCart>(store.createCart());
183
+ const [version, setVersion] = useState(0);
184
+ const [checkingOut, setCheckingOut] = useState(false);
185
+ const [lastCheckout, setLastCheckout] = useState<CheckoutResult | null>(null);
186
+ const [error, setError] = useState<string | null>(null);
187
+
188
+ const bump = useCallback(() => setVersion((v) => v + 1), []);
189
+
190
+ const items = useMemo(() => cartRef.current.getItems(), [version]);
191
+ const totals = useMemo(() => cartRef.current.getTotals(), [version]);
192
+ const isEmpty = useMemo(() => cartRef.current.isEmpty, [version]);
193
+
194
+ const addItem = useCallback(
195
+ (productId: string, quantity: number = 1) => {
196
+ try {
197
+ cartRef.current.addItem(productId, quantity);
198
+ setError(null);
199
+ } catch (e: any) {
200
+ setError(e.message);
201
+ }
202
+ bump();
203
+ },
204
+ [bump]
205
+ );
206
+
207
+ const setQuantity = useCallback(
208
+ (productId: string, quantity: number) => {
209
+ try {
210
+ cartRef.current.setItemQuantity(productId, quantity);
211
+ setError(null);
212
+ } catch (e: any) {
213
+ setError(e.message);
214
+ }
215
+ bump();
216
+ },
217
+ [bump]
218
+ );
219
+
220
+ const removeItem = useCallback(
221
+ (productId: string) => {
222
+ cartRef.current.removeItem(productId);
223
+ bump();
224
+ },
225
+ [bump]
226
+ );
227
+
228
+ const clear = useCallback(() => {
229
+ cartRef.current.clear();
230
+ setLastCheckout(null);
231
+ setError(null);
232
+ bump();
233
+ }, [bump]);
234
+
235
+ const applyDiscount = useCallback(
236
+ (code: string) => {
237
+ const ok = cartRef.current.applyDiscount(code);
238
+ bump();
239
+ return ok;
240
+ },
241
+ [bump]
242
+ );
243
+
244
+ const removeDiscount = useCallback(
245
+ (code: string) => {
246
+ const ok = cartRef.current.removeDiscount(code);
247
+ bump();
248
+ return ok;
249
+ },
250
+ [bump]
251
+ );
252
+
253
+ const setMetadata = useCallback(
254
+ (key: string, value: string) => {
255
+ cartRef.current.setMetadata(key, value);
256
+ },
257
+ []
258
+ );
259
+
260
+ const checkout = useCallback(
261
+ async (options?: {
262
+ currency?: Currency;
263
+ expiresIn?: number;
264
+ allowTips?: boolean;
265
+ orderRef?: string;
266
+ }) => {
267
+ setCheckingOut(true);
268
+ setError(null);
269
+ try {
270
+ const result = await cartRef.current.checkout(options);
271
+ setLastCheckout(result);
272
+ // Create a fresh cart after successful checkout
273
+ cartRef.current = store.createCart();
274
+ bump();
275
+ return result;
276
+ } catch (e: any) {
277
+ setError(e.message);
278
+ throw e;
279
+ } finally {
280
+ setCheckingOut(false);
281
+ }
282
+ },
283
+ [store, bump]
284
+ );
285
+
286
+ return {
287
+ cart: cartRef.current,
288
+ items,
289
+ totals,
290
+ isEmpty,
291
+ addItem,
292
+ setQuantity,
293
+ removeItem,
294
+ clear,
295
+ applyDiscount,
296
+ removeDiscount,
297
+ setMetadata,
298
+ checkout,
299
+ checkingOut,
300
+ lastCheckout,
301
+ error,
302
+ };
303
+ }
304
+
305
+ // ==================== useMarketplace ====================
306
+
307
+ export interface UseMarketplaceReturn {
308
+ /** All vendors */
309
+ vendors: Vendor[];
310
+ /** All marketplace products */
311
+ products: MarketplaceProduct[];
312
+ /** Categories */
313
+ categories: string[];
314
+ /** Get products by vendor */
315
+ getVendorProducts: (vendorId: string) => MarketplaceProduct[];
316
+ /** Search products across all vendors */
317
+ search: (query: string) => MarketplaceProduct[];
318
+ /** Check stock */
319
+ isInStock: (productId: string, qty?: number) => boolean;
320
+ /** Get a specific vendor */
321
+ getVendor: (id: string) => Vendor | undefined;
322
+ }
323
+
324
+ /**
325
+ * Hook for browsing a marketplace catalog.
326
+ */
327
+ export function useMarketplace(marketplace: RotateMarketplace): UseMarketplaceReturn {
328
+ const [version, setVersion] = useState(0);
329
+
330
+ const vendors = useMemo(
331
+ () => marketplace.getVendors({ active: true }),
332
+ [marketplace, version]
333
+ );
334
+
335
+ const products = useMemo(
336
+ () => marketplace.getProducts({ active: true }),
337
+ [marketplace, version]
338
+ );
339
+
340
+ const categories = useMemo(
341
+ () => marketplace.getCategories(),
342
+ [marketplace, version]
343
+ );
344
+
345
+ const getVendorProducts = useCallback(
346
+ (vendorId: string) => marketplace.getVendorProducts(vendorId),
347
+ [marketplace, version]
348
+ );
349
+
350
+ const search = useCallback(
351
+ (query: string) => marketplace.getProducts({ search: query, active: true }),
352
+ [marketplace, version]
353
+ );
354
+
355
+ const isInStock = useCallback(
356
+ (productId: string, qty: number = 1) => marketplace.isInStock(productId, qty),
357
+ [marketplace]
358
+ );
359
+
360
+ const getVendor = useCallback(
361
+ (id: string) => marketplace.getVendor(id),
362
+ [marketplace]
363
+ );
364
+
365
+ return {
366
+ vendors,
367
+ products,
368
+ categories,
369
+ getVendorProducts,
370
+ search,
371
+ isInStock,
372
+ getVendor,
373
+ };
374
+ }
375
+
376
+ // ==================== useMarketplaceCart ====================
377
+
378
+ export interface UseMarketplaceCartReturn {
379
+ /** The underlying marketplace cart */
380
+ cart: MarketplaceCart;
381
+ /** Current line items with vendor info */
382
+ items: MarketplaceLineItem[];
383
+ /** Per-vendor + combined totals */
384
+ totals: MarketplaceCartTotals;
385
+ /** Is empty */
386
+ isEmpty: boolean;
387
+ /** Number of unique vendors in cart */
388
+ vendorCount: number;
389
+ /** Add item */
390
+ addItem: (productId: string, quantity?: number) => void;
391
+ /** Set quantity */
392
+ setQuantity: (productId: string, quantity: number) => void;
393
+ /** Remove item */
394
+ removeItem: (productId: string) => void;
395
+ /** Clear */
396
+ clear: () => void;
397
+ /** Checkout — creates one payment link per vendor */
398
+ checkout: (options?: {
399
+ currency?: Currency;
400
+ expiresIn?: number;
401
+ allowTips?: boolean;
402
+ orderRef?: string;
403
+ }) => Promise<MarketplaceCheckoutResult>;
404
+ /** Whether checkout is in progress */
405
+ checkingOut: boolean;
406
+ /** Last checkout result */
407
+ lastCheckout: MarketplaceCheckoutResult | null;
408
+ /** Error */
409
+ error: string | null;
410
+ }
411
+
412
+ /**
413
+ * Hook for managing a multi-vendor marketplace cart.
414
+ */
415
+ export function useMarketplaceCart(marketplace: RotateMarketplace): UseMarketplaceCartReturn {
416
+ const cartRef = useRef<MarketplaceCart>(marketplace.createCart());
417
+ const [version, setVersion] = useState(0);
418
+ const [checkingOut, setCheckingOut] = useState(false);
419
+ const [lastCheckout, setLastCheckout] = useState<MarketplaceCheckoutResult | null>(null);
420
+ const [error, setError] = useState<string | null>(null);
421
+
422
+ const bump = useCallback(() => setVersion((v) => v + 1), []);
423
+
424
+ const items = useMemo(() => cartRef.current.getItems(), [version]);
425
+ const totals = useMemo(() => cartRef.current.getTotals(), [version]);
426
+ const isEmpty = useMemo(() => cartRef.current.isEmpty, [version]);
427
+
428
+ const addItem = useCallback(
429
+ (productId: string, quantity: number = 1) => {
430
+ try {
431
+ cartRef.current.addItem(productId, quantity);
432
+ setError(null);
433
+ } catch (e: any) {
434
+ setError(e.message);
435
+ }
436
+ bump();
437
+ },
438
+ [bump]
439
+ );
440
+
441
+ const setQuantity = useCallback(
442
+ (productId: string, quantity: number) => {
443
+ try {
444
+ cartRef.current.setItemQuantity(productId, quantity);
445
+ setError(null);
446
+ } catch (e: any) {
447
+ setError(e.message);
448
+ }
449
+ bump();
450
+ },
451
+ [bump]
452
+ );
453
+
454
+ const removeItem = useCallback(
455
+ (productId: string) => {
456
+ cartRef.current.removeItem(productId);
457
+ bump();
458
+ },
459
+ [bump]
460
+ );
461
+
462
+ const clear = useCallback(() => {
463
+ cartRef.current.clear();
464
+ setLastCheckout(null);
465
+ setError(null);
466
+ bump();
467
+ }, [bump]);
468
+
469
+ const checkout = useCallback(
470
+ async (options?: {
471
+ currency?: Currency;
472
+ expiresIn?: number;
473
+ allowTips?: boolean;
474
+ orderRef?: string;
475
+ }) => {
476
+ setCheckingOut(true);
477
+ setError(null);
478
+ try {
479
+ const result = await cartRef.current.checkout(options);
480
+ setLastCheckout(result);
481
+ cartRef.current = marketplace.createCart();
482
+ bump();
483
+ return result;
484
+ } catch (e: any) {
485
+ setError(e.message);
486
+ throw e;
487
+ } finally {
488
+ setCheckingOut(false);
489
+ }
490
+ },
491
+ [marketplace, bump]
492
+ );
493
+
494
+ return {
495
+ cart: cartRef.current,
496
+ items,
497
+ totals,
498
+ isEmpty,
499
+ vendorCount: totals.vendorCount,
500
+ addItem,
501
+ setQuantity,
502
+ removeItem,
503
+ clear,
504
+ checkout,
505
+ checkingOut,
506
+ lastCheckout,
507
+ error,
508
+ };
509
+ }
510
+
511
+ // ==================== EXPORTS ====================
512
+
513
+ export {
514
+ RotateStore,
515
+ RotateCart,
516
+ RotateMarketplace,
517
+ MarketplaceCart,
518
+ };